diff --git a/.asf.yaml b/.asf.yaml index 4d979a188335..cb957e57fd95 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -16,7 +16,6 @@ # under the License. # https://cwiki.apache.org/confluence/display/INFRA/git+-+.asf.yaml+features ---- github: description: "Apache CloudStack is an opensource Infrastructure as a Service (IaaS) cloud computing platform" homepage: https://cloudstack.apache.org/ @@ -50,21 +49,34 @@ github: rebase: false collaborators: - - acs-robot - - rajujith + - ingox + - gp-santos + - erikbocks + - Imvedansh + - Damans227 + - jmsperu - GaOrtiga - - SadiJr - - winterhazel - - gpordeus - - hsato03 - - bernardodemarco - - abh1sar - - FelipeM525 + - bhouse-nexthop - protected_branches: ~ + rulesets: + - name: "Default Branch Protection" + type: branch + branches: + includes: + - "~DEFAULT_BRANCH" + excludes: [] + bypass_teams: + - root + restrict_deletion: true + restrict_force_push: true + + copilot_code_review: + enabled: true + review_drafts: true + review_on_push: true notifications: - commits: commits@cloudstack.apache.org - issues: commits@cloudstack.apache.org + commits: commits@cloudstack.apache.org + issues: commits@cloudstack.apache.org pullrequests: commits@cloudstack.apache.org - discussions: users@cloudstack.apache.org + discussions: users@cloudstack.apache.org diff --git a/.codespellrc b/.codespellrc new file mode 100644 index 000000000000..3c632f8ba534 --- /dev/null +++ b/.codespellrc @@ -0,0 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +[codespell] +ignore-words = .github/linters/codespell.txt +skip = systemvm/agent/noVNC/*,ui/package.json,ui/package-lock.json,ui/public/js/less.min.js,ui/public/locales/*.json,server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest.java,test/integration/smoke/test_ssl_offloading.py diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000000..1b06f3ebf53c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +.github/workflows/*.lock.yml linguist-generated=true merge=ours diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 000000000000..375912667d63 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +/plugins/storage/volume/linstor @rp- +/plugins/storage/volume/storpool @slavkap +/plugins/storage/volume/ontap @rajiv-jain-netapp @sandeeplocharla @piyush5netapp @suryag1201 + +.pre-commit-config.yaml @jbampton +/.github/linters/ @jbampton + +/plugins/network-elements/nsx/ @Pearl1594 @nvazquez diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 000000000000..10f05a1adfdf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,46 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +description: "Thank you for reporting a bug!" +name: bug +title: "[SHORT PROBLEM DESCRIPTION]" +labels: bug, needs-triageing +body: +- type: markdown + attributes: + value: "## Welcome, please describe your problem below;" +- type: textarea + attributes: + label: problem + value: The long description of your problem +- type: markdown + attributes: + value: "## What versions of cloudstack and any infra components are you using" +- type: textarea + attributes: + label: versions + value: The versions of ACS, hypervisors, storage, network etc.. +- type: textarea + attributes: + label: The steps to reproduce the bug + value: | + 1. + 2. + 3. + ... +- type: textarea + attributes: + label: "What to do about it?" diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000000..90de11698861 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +description: "Thank you for your new feature idea!" +name: feature +title: "[SHORT FUNCTIONAL DESCRIPTION]" +labels: new +body: +- type: textarea + attributes: + label: "The required feature described as a wish" + value: As a User/Admin/Operator I would like to , ... have the system make my morning coffee. diff --git a/.github/actions/install-nonoss/action.yml b/.github/actions/install-nonoss/action.yml new file mode 100644 index 000000000000..39a03213c29d --- /dev/null +++ b/.github/actions/install-nonoss/action.yml @@ -0,0 +1,31 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +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/aw/actions-lock.json b/.github/aw/actions-lock.json new file mode 100644 index 000000000000..ea25ffab6b9e --- /dev/null +++ b/.github/aw/actions-lock.json @@ -0,0 +1,56 @@ +{ + "entries": { + "actions/github-script@v9.0.0": { + "repo": "actions/github-script", + "version": "v9.0.0", + "sha": "3a2844b7e9c422d3c10d287c895573f7108da1b3" + }, + "github/gh-aw-actions/setup@v0.76.1": { + "repo": "github/gh-aw-actions/setup", + "version": "v0.76.1", + "sha": "46d564922b082d0db93244972e8005ea6904ee5f" + } + }, + "containers": { + "ghcr.io/github/gh-aw-firewall/agent:0.18.0": { + "image": "ghcr.io/github/gh-aw-firewall/agent:0.18.0", + "digest": "sha256:ab84dfc7f5998cb8cd0c596526dd573b7e7d06c6a740266a1e6df879fa16c866", + "pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.18.0@sha256:ab84dfc7f5998cb8cd0c596526dd573b7e7d06c6a740266a1e6df879fa16c866" + }, + "ghcr.io/github/gh-aw-firewall/agent:0.25.55": { + "image": "ghcr.io/github/gh-aw-firewall/agent:0.25.55", + "digest": "sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731", + "pinned_image": "ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731" + }, + "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55": { + "image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55", + "digest": "sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3", + "pinned_image": "ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3" + }, + "ghcr.io/github/gh-aw-firewall/squid:0.18.0": { + "image": "ghcr.io/github/gh-aw-firewall/squid:0.18.0", + "digest": "sha256:82a5d062a5612a57a43a171a5b79ddbb690a86a8ddda02339cc1675131ae9f8b", + "pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.18.0@sha256:82a5d062a5612a57a43a171a5b79ddbb690a86a8ddda02339cc1675131ae9f8b" + }, + "ghcr.io/github/gh-aw-firewall/squid:0.25.55": { + "image": "ghcr.io/github/gh-aw-firewall/squid:0.25.55", + "digest": "sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca", + "pinned_image": "ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca" + }, + "ghcr.io/github/gh-aw-mcpg:v0.1.4": { + "image": "ghcr.io/github/gh-aw-mcpg:v0.1.4", + "digest": "sha256:0acf25aa1d409f9c73be9e39ac84f4bd4b90d8bfa1db4dc6d7f47d38ccd58914", + "pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.1.4@sha256:0acf25aa1d409f9c73be9e39ac84f4bd4b90d8bfa1db4dc6d7f47d38ccd58914" + }, + "ghcr.io/github/gh-aw-mcpg:v0.3.19": { + "image": "ghcr.io/github/gh-aw-mcpg:v0.3.19", + "digest": "sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f", + "pinned_image": "ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f" + }, + "ghcr.io/github/github-mcp-server:v0.30.3": { + "image": "ghcr.io/github/github-mcp-server:v0.30.3", + "digest": "sha256:a2b5fb79b1cee851bfc3532dfe480c3dc5736974ca9d93a7a9f68e52ce4b62a0", + "pinned_image": "ghcr.io/github/github-mcp-server:v0.30.3@sha256:a2b5fb79b1cee851bfc3532dfe480c3dc5736974ca9d93a7a9f68e52ce4b62a0" + } + } +} diff --git a/.github/aw/imports/.gitattributes b/.github/aw/imports/.gitattributes new file mode 100644 index 000000000000..f0516fad90e4 --- /dev/null +++ b/.github/aw/imports/.gitattributes @@ -0,0 +1,5 @@ +# Mark all cached import files as generated +* linguist-generated=true + +# Use 'ours' merge strategy to keep local cached versions +* merge=ours diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000000..6ffb926f6fa0 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,43 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + open-pull-requests-limit: 2 + schedule: + interval: "weekly" + groups: + github-actions-dependencies: + patterns: + - "*" + ignore: + - dependency-name: "github/gh-aw-actions/**" # Managed by gh aw compile. Version-locked to the gh-aw compiler; do not bump. + cooldown: + default-days: 7 + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "daily" + cooldown: + default-days: 7 diff --git a/.github/linters/.markdown-lint.yml b/.github/linters/.markdown-lint.yml index df1b1a2825e3..6a8a26f48497 100644 --- a/.github/linters/.markdown-lint.yml +++ b/.github/linters/.markdown-lint.yml @@ -18,36 +18,21 @@ # MD001/heading-increment Heading levels should only increment by one level at a time MD001: false -# MD003/heading-style Heading style -MD003: false - # MD004/ul-style Unordered list style MD004: false # MD007/ul-indent Unordered list indentation MD007: false -# MD009/no-trailing-spaces Trailing spaces -MD009: false - # MD010/no-hard-tabs Hard tabs MD010: false -# MD012/no-multiple-blanks Multiple consecutive blank lines -MD012: false - # MD013/line-length Line length MD013: false # MD014/commands-show-output Dollar signs used before commands without showing output MD014: false -# MD018/no-missing-space-atx No space after hash on atx style heading -MD018: false - -# MD019/no-multiple-space-atx Multiple spaces after hash on atx style heading -MD019: false - # MD022/blanks-around-headings Headings should be surrounded by blank lines MD022: false @@ -98,3 +83,6 @@ MD046: false # MD052/reference-links-images Reference links and images should use a label that is defined MD052: false + +# MD059/descriptive-link-text Link text should be descriptive +MD059: false diff --git a/.github/linters/.yamllint.yml b/.github/linters/.yamllint.yml new file mode 100644 index 000000000000..97b66848696a --- /dev/null +++ b/.github/linters/.yamllint.yml @@ -0,0 +1,32 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +--- +extends: default + +rules: + line-length: + max: 400 # Very forgiving for GitHub Actions and infrastructure files + indentation: disable # Disable indentation checking for existing files + comments: disable # Disable comment formatting checks + braces: disable + brackets: disable # Disable bracket spacing checks + colons: + max-spaces-after: -1 # Allow any number of spaces after colon + max-spaces-before: 0 + document-start: disable # Many files don't have --- + truthy: + allowed-values: ['true', 'false', 'on', 'off', 'yes', 'no'] diff --git a/.github/linters/codespell.txt b/.github/linters/codespell.txt index 37b3e6de1cbe..67cbeaa7cbb3 100644 --- a/.github/linters/codespell.txt +++ b/.github/linters/codespell.txt @@ -4,6 +4,7 @@ acount actuall acuiring acumulate +addin addreess addtion adminstrator @@ -12,10 +13,8 @@ afrer afterall againt ags -aktive algoritm allo -alloacate allocted alocation alogrithm @@ -65,6 +64,7 @@ bject boardcast bootstraper bu +callin cant capabilites capablity @@ -73,6 +73,7 @@ carrefully cavaet chaing checkd +checkin childs choosen chould @@ -93,7 +94,6 @@ confg configruation configuable conneciton -connexion constrait constraits containg @@ -101,9 +101,7 @@ contex continuesly contro controler -controles controll -convienient convinience coputer correcponding @@ -158,13 +156,13 @@ differnet differnt direcotry directroy -disale disbale discrepency disover dissapper dissassociated divice +dockin doesn' doesnot doesnt @@ -175,7 +173,6 @@ eanbled earch ect elemnt -eles elments emmited enble @@ -183,29 +180,23 @@ encryted enebled enmpty entires -enviornment environmnet equivalant erro erronous -everthing everytime -excetion -excption excute execept execption +exects execut executeable exeeded exisitng exisits -existin existsing -exitting expcted expection -explaination explicitely faield faild @@ -218,7 +209,6 @@ fillled findout fisrt fo -folowing fowarding frist fro @@ -237,6 +227,7 @@ hanling happend hasing hasnt +havin hda hostanme hould @@ -256,20 +247,14 @@ implmeneted implmentation incase includeing -incosistency indecates -indien infor informations informaton -infrastrcuture ingore -inital initalize initator -initilization inspite -instace instal instnace intefaces @@ -287,12 +272,8 @@ ist klunky lable leve -lief limite -linke listner -lokal -lokales maintainence maintenace maintenence @@ -301,7 +282,6 @@ mambers manaully manuel maxium -mehtod mergable mesage messge @@ -311,7 +291,6 @@ minumum mis modifers mor -mot mulitply multipl multple @@ -325,7 +304,7 @@ nin nodel nome noone -nowe +notin numbe numer occured @@ -378,6 +357,7 @@ propogate provison psudo pyhsical +re-use readabilty readd reccuring @@ -392,12 +372,9 @@ remaning remore remvoing renabling -repeatly reponse reqest reqiured -requieres -requried reserv reserverd reseted @@ -414,17 +391,15 @@ retriving retrun retuned returing -re-use rever rocessor +roperty runing runnign sate scalled -scipt scirpt scrip -seconadry seconday seesion sepcified @@ -437,12 +412,10 @@ settig sevices shoul shoule -sie signle simplier singature skiping -snaphsot snpashot specied specifed @@ -453,7 +426,6 @@ standy statics stickyness stil -stip storeage strat streched @@ -462,7 +434,6 @@ succesfull successfull suceessful suces -sucessfully suiteable suppots suppport @@ -495,7 +466,6 @@ uncompressible uneccessarily unexepected unexpect -unknow unkonw unkown unneccessary @@ -503,14 +473,12 @@ unparseable unrecoginized unsupport unxpected -updat uptodate usera usign usin utlization vaidate -valiate valule valus varibles @@ -519,8 +487,6 @@ verfying verifing virutal visable -wakup wil wit -wll wth diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index fd3c8f8ac67d..9c8ca10f85e9 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -16,41 +16,31 @@ # under the License. name: Build - -on: [push, pull_request] - +on: + push: + branches: + - main + - 4.22 + - 4.20 + 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@v4 - - - name: Set up JDK 11 - uses: actions/setup-java@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - java-version: '11' - distribution: 'adopt' - architecture: x64 - cache: maven + persist-credentials: false - - name: Set up Python - uses: actions/setup-python@v5 + - 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 @@ -61,9 +51,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 efd71b5da5df..7f6645f4af4d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,21 +16,60 @@ # under the License. name: Simulator CI - -on: [push, pull_request] - +on: + push: + branches: + - main + - 4.22 + - 4.20 + 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@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + 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: @@ -89,7 +128,10 @@ jobs: smoke/test_nested_virtualization smoke/test_set_sourcenat smoke/test_webhook_lifecycle - smoke/test_purge_expunged_vms", + smoke/test_purge_expunged_vms + smoke/test_extension_lifecycle + smoke/test_extension_custom_action_lifecycle + smoke/test_extension_custom", "smoke/test_network smoke/test_network_acl smoke/test_network_ipv6 @@ -137,11 +179,13 @@ jobs: smoke/test_vm_deployment_planner smoke/test_vm_strict_host_tags smoke/test_vm_schedule + smoke/test_deploy_vgpu_enabled_vm smoke/test_vm_life_cycle smoke/test_vm_lifecycle_unmanage_import smoke/test_vm_snapshot_kvm smoke/test_vm_snapshots smoke/test_volumes + smoke/test_vpc_conserve_mode smoke/test_vpc_ipv6 smoke/test_vpc_redundant smoke/test_vpc_router_nics @@ -164,7 +208,8 @@ jobs: component/test_cpu_limits component/test_cpu_max_limits component/test_cpu_project_limits - component/test_deploy_vm_userdata_multi_nic", + component/test_deploy_vm_userdata_multi_nic + component/test_deploy_vm_lease", "component/test_egress_fw_rules component/test_invalid_gw_nm component/test_ip_reservation", @@ -209,31 +254,16 @@ jobs: smoke/test_list_service_offerings smoke/test_list_storage_pools smoke/test_list_volumes"] - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 - - - name: Set up JDK - uses: actions/setup-java@v4 + persist-credentials: false + - name: Setup Environment + uses: ./.github/actions/setup-env with: - java-version: '11' - distribution: 'adopt' - architecture: x64 - cache: maven - - - name: Set up Python - uses: actions/setup-python@v5 - 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 @@ -251,28 +281,13 @@ 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 @@ -281,25 +296,28 @@ jobs: 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" @@ -310,7 +328,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 @@ -330,13 +347,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@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 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 c77783746ca1..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@v4 - with: - fetch-depth: 0 - - - name: Set up JDK 17 - uses: actions/setup-java@v4 - 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 new file mode 100644 index 000000000000..f6bc65ad4b5b --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,50 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: CodeQL Analysis +on: + push: + branches: [main] + pull_request: + branches: [main] +permissions: + actions: read + contents: read + security-events: write +jobs: + codeql: + name: CodeQL + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: ["actions"] + steps: + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - name: Initialize CodeQL + uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + languages: ${{ matrix.language }} + - name: Autobuild + uses: github/codeql-action/autobuild@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 + with: + category: "Security" diff --git a/.github/workflows/daily-issue-triage.lock.yml b/.github/workflows/daily-issue-triage.lock.yml new file mode 100644 index 000000000000..bd07aeefd811 --- /dev/null +++ b/.github/workflows/daily-issue-triage.lock.yml @@ -0,0 +1,1400 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"919fb17c7928e5e96d9c0a2854670a42f9c5f6cfc2059b46009bb3c23640d0ca","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55","digest":"sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55","digest":"sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55","digest":"sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19","digest":"sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.76.1). DO NOT EDIT. +# +# To update this file, edit githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9 and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# Scheduled daily triage that processes untriaged CloudStack issues in batches. +# Detects duplicates, filters spam, and assigns CloudStack-specific labels +# (type:*, component:*, Severity:*, status:*), then posts a structured triage report. +# +# Source: githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9 +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca +# - ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f +# - ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 +# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + +name: "Daily Issue Triage" +on: + schedule: + - cron: "26 13 * * 1-5" + # Friendly format: daily around 14:00 on weekdays (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Daily Issue Triage" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AGENT_VERSION: "1.0.52" + GH_AW_INFO_CLI_VERSION: "v0.76.1" + GH_AW_INFO_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_INFO_FRONTMATTER_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .antigravity + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "daily-issue-triage.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.76.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + + GH_AW_PROMPT_7c51e8f15cc7af75_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + + Tools: add_comment(max:10), add_labels(max:10), missing_tool, missing_data, noop + + GH_AW_PROMPT_7c51e8f15cc7af75_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_7c51e8f15cc7af75_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_7c51e8f15cc7af75_EOF' + + {{#runtime-import .github/workflows/daily-issue-triage.md}} + GH_AW_PROMPT_7c51e8f15cc7af75_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: read-all + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: dailyissuetriage + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3 ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + - name: Generate Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_09fd9551c3cd7278_EOF' + {"add_comment":{"max":10,"target":"*"},"add_labels":{"max":10,"target":"*"},"create_report_incomplete_issue":{},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_09fd9551c3cd7278_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "add_comment": " CONSTRAINTS: Maximum 10 comment(s) can be added. Target: *. Supports reply_to_id for discussion threading.", + "add_labels": " CONSTRAINTS: Maximum 10 label(s) can be added. Target: *." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "add_comment": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "item_number": { + "issueOrPRNumber": true + }, + "reply_to_id": { + "type": "string", + "maxLength": 256 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "add_labels": { + "defaultMax": 5, + "fields": { + "item_number": { + "issueNumberOrTemporaryId": true + }, + "labels": { + "required": true, + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "repo": { + "type": "string", + "maxLength": 256 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.19' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_37cac1d5ee0c175c_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "issues,labels" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": "all", + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_37cac1d5ee0c175c_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 60 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55,squid=sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca,agent=sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731,api-proxy=sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_AGENT_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.76.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + concurrency: + group: "gh-aw-conclusion-daily-issue-triage" + cancel-in-progress: false + queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "daily-issue-triage" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "60" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3 ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "Daily Issue Triage" + WORKFLOW_DESCRIPTION: "Scheduled daily triage that processes untriaged CloudStack issues in batches.\nDetects duplicates, filters spam, and assigns CloudStack-specific labels\n(type:*, component:*, Severity:*, status:*), then posts a structured triage report." + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: ${{ vars.GH_AW_MODEL_DETECTION_COPILOT || 'claude-sonnet-4.6' }} + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.76.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + discussions: write + issues: write + pull-requests: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-issue-triage" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: ${{ needs.agent.outputs.model }} + GH_AW_ENGINE_VERSION: "1.0.52" + GH_AW_WORKFLOW_ID: "daily-issue-triage" + GH_AW_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/d7c1dc4b72b00607a67caaffdcc216cb64379cf9/workflows/daily-issue-triage.md" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + comment_id: ${{ steps.process_safe_outputs.outputs.comment_id }} + comment_url: ${{ steps.process_safe_outputs.outputs.comment_url }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Daily Issue Triage" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-issue-triage.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + 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,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"add_comment\":{\"max\":10,\"target\":\"*\"},\"add_labels\":{\"max\":10,\"target\":\"*\"},\"create_report_incomplete_issue\":{},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore diff --git a/.github/workflows/daily-issue-triage.md b/.github/workflows/daily-issue-triage.md new file mode 100644 index 000000000000..719dca1f3c63 --- /dev/null +++ b/.github/workflows/daily-issue-triage.md @@ -0,0 +1,171 @@ +--- +description: | + Scheduled daily triage that processes untriaged CloudStack issues in batches. + Detects duplicates, filters spam, and assigns CloudStack-specific labels + (type:*, component:*, Severity:*, status:*), then posts a structured triage report. + +name: Daily Issue Triage + +on: + schedule: daily around 14:00 on weekdays + workflow_dispatch: + +permissions: read-all + +network: defaults + +safe-outputs: + add-labels: + target: "*" + max: 10 + add-comment: + target: "*" + max: 10 + +tools: + web-fetch: + github: + toolsets: [issues, labels] + min-integrity: none + +source: githubnext/agentics/workflows/daily-issue-triage.md@d7c1dc4b72b00607a67caaffdcc216cb64379cf9 +timeout-minutes: 60 +--- + +# Daily Issue Triage + + + +You are a batch triage assistant for GitHub issues in **${{ github.repository }}** (Apache CloudStack). Your task is to find untriaged issues and triage them one by one. Your triage comments are written for maintainers reviewing the triage, not for the issue author. + +Do not make assumptions beyond what the issue content supports. Do not invent missing context. + +## Step 1: Find untriaged issues + +Use the `search_issues` tool to find open issues that need triage. An issue is considered untriaged if it has **no labels applied**. + +Query: `repo:${{ github.repository }} is:issue is:open no:label` + +Paginate through all results to find untriaged issues. Do not stop at the first page. + +From the results, filter out: +- Issues that already have a triage comment (look for "🎯 Triage report" in comments). **Never retriage an issue that has already been triaged.** +- Issues created by bots (unless they look like real user issues). +- Issues that have any labels already applied (even if they weren't applied by this workflow). + +Process the **oldest untriaged issues first**. Note: this workflow is capped at 10 label-sets and 10 comments per run, so the backlog will drain over several daily runs — that is intentional. + +## Step 2: Fetch labels (once) + +Before triaging any issues, fetch the list of labels available in this repository using the `list_labels` tool. Use this live list for all issues in the batch — only apply labels that actually exist in the repository. + +CloudStack uses a prefixed label taxonomy. Choose from these families: + +- **Type** (pick the single best one): `type:bug`, `type:new-feature`, `type:enhancement`, `type:improvement`, `type:regression`, `type:security`, `type:question`, `type:config`, `type:cleanup` +- **Component** (apply when clearly identifiable; more than one is allowed): e.g. `component:kvm`, `component:vmware`, `component:XenServer`, `component:api`, `component:UI`, `component:networking`, `component:virtual-router`, `component:management-server`, `component:primary-storage`, `component:secondary-storage`, `component:kubernetes`, `component:database`, and others — use the full list returned by `list_labels`. +- **Severity** (bugs only, when assessable): `Severity:BLOCKER`, `Severity:Critical`, `Severity:Major`, `Severity:Minor`, `Severity:Trivial` +- **Duplicate / invalid**: `status:duplicate`, `status:invalid` +- **Help wanted / newcomer-friendly**: `status:Help-wanted` + +## Step 3: Triage each issue + +For each untriaged issue, perform the following steps: + +### 3a: Gather context + +1. Retrieve the full issue content using the `get_issue` tool. +2. Fetch any comments on the issue using the `get_issue_comments` tool. +3. Search for similar issues using the `search_issues` tool. + +### 3b: Spam and quality check + +**Spam and invalid issues:** If the issue is obviously spam, bot-generated, gibberish, or a test issue: +- Apply the `status:invalid` label. +- **Do not close the issue** — closing is a human decision. Note in the report that it looks like spam/invalid so a maintainer can act. +- Move to the next issue. + +**Incomplete issues:** If the issue lacks enough detail for meaningful triage, add a comment that politely asks the author to provide the missing information: +- For bugs: steps to reproduce, expected vs actual behavior, logs/errors, environment details (CloudStack version, hypervisor, etc.). +- For other issue types: equivalent details that would make the report actionable. +- Apply a `type:question` label if appropriate. +- Be specific about what is missing and why it is needed. +- Move to the next issue. + +### 3c: Select labels + +- Be cautious with labels; they can trigger automation. +- Choose a single `type:*` label that best reflects the issue's nature. +- Add `component:*` label(s) when the affected area is clear from the content. +- Add a `Severity:*` label for bugs when severity can be reasonably assessed. +- Do not apply labels that do not exist in the repository. +- It is better to under-label than to speculatively add labels. + +### 3d: Detect duplicates and related issues + +- Review the similar issues found in Step 3a. +- Classify matches as: + - **Duplicate** (high confidence): the issue describes the same problem as an existing open issue. Include up to 3. + - **Related**: similar domain or adjacent problem, but not a duplicate. Include up to 3. +- If a high-confidence duplicate is found, apply the `status:duplicate` label. +- If no similar issues are found, state that explicitly in your report. + +### 3e: Assess coding agent suitability + +Assess whether the issue is suitable for automated coding agent assignment: +- **Suitable**: clear requirements, sufficient context, well-defined success criteria, self-contained scope. +- **Needs more info**: potentially suitable but missing details needed to start. +- **Not suitable**: requires investigation, design decisions, extensive coordination, or policy/architectural choices. + +### 3f: Additional analysis + +- Search the web for relevant documentation, error messages, or known solutions if applicable. +- Write notes, debugging strategies, and/or reproduction steps relevant to the issue. +- Suggest resources or links that might help resolve the issue. + +### 3g: Apply results and post comment + +Apply all triage results for this issue: +- Use `update_issue` to apply the chosen labels. +- Add an issue comment with the triage report using the format below. + +Then move to the next issue. + +## Processing order + +1. Fetch available labels (Step 2, once at the start). +2. Find untriaged issues (Step 1). +3. For each issue (oldest first), run Step 3 (gather, check, label, detect duplicates, comment). + +## Comment format + +Use this structure for each triage comment. Use collapsed sections to keep it tidy. + +```markdown +## 🎯 Triage report + +{2-3 sentence summary to help a maintainer quickly grasp the issue.} + +### 📊 Assessment + +| Dimension | Value | Reasoning | +|---|---|---| +| **Type** | [type:* label or "none"] | [brief] | +| **Component** | [component:* label(s) or "none"] | [brief] | +| **Severity** | [Severity:* label or "n/a"] | [brief] | +| **Labels** | [all labels applied or "none"] | [brief] | +| **Coding agent** | [Suitable / Needs more info / Not suitable] | [brief] | + +### 🔗 Similar issues + +- issue-url (duplicate/related) — [brief explanation] + +
💡 Notes and suggestions + +{Debugging strategies, reproduction steps, resource links, sub-task checklists, nudges for the team.} + +
+``` + +If no similar issues were found, omit the "Similar issues" section. If there are no notes to add, omit the collapsed section. + +**Important**: Never close issues. Only apply labels and post comments. diff --git a/.github/workflows/daily-repo-status.lock.yml b/.github/workflows/daily-repo-status.lock.yml new file mode 100644 index 000000000000..0992d3b67de0 --- /dev/null +++ b/.github/workflows/daily-repo-status.lock.yml @@ -0,0 +1,1394 @@ +# gh-aw-metadata: {"schema_version":"v3","frontmatter_hash":"bcecce6f1d9f8df2b3eca9eb7bb1fdbac13c396c240a2dc802a96546f435b969","compiler_version":"v0.76.1","strict":true,"agent_id":"copilot","agent_model":"claude-haiku-4.5"} +# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"de0fac2e4500dabe0009e67214ff5f5447ce83dd","version":"v6.0.2"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"46d564922b082d0db93244972e8005ea6904ee5f","version":"v0.76.1"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55","digest":"sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55","digest":"sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55","digest":"sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.19","digest":"sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f"},{"image":"ghcr.io/github/github-mcp-server:v1.0.4","digest":"sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4","pinned_image":"ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4"},{"image":"node:lts-alpine","digest":"sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14","pinned_image":"node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14"}]} +# ___ _ _ +# / _ \ | | (_) +# | |_| | __ _ ___ _ __ | |_ _ ___ +# | _ |/ _` |/ _ \ '_ \| __| |/ __| +# | | | | (_| | __/ | | | |_| | (__ +# \_| |_/\__, |\___|_| |_|\__|_|\___| +# __/ | +# _ _ |___/ +# | | | | / _| | +# | | | | ___ _ __ _ __| |_| | _____ ____ +# | |/\| |/ _ \ '__| |/ /| _| |/ _ \ \ /\ / / ___| +# \ /\ / (_) | | | | ( | | | | (_) \ V V /\__ \ +# \/ \/ \___/|_| |_|\_\|_| |_|\___/ \_/\_/ |___/ +# +# This file was automatically generated by gh-aw (v0.76.1). DO NOT EDIT. +# +# To update this file, edit githubnext/agentics/workflows/repo-status.md@main and run: +# gh aw compile +# Not all edits will cause changes to this file. +# +# For more information: https://github.github.com/gh-aw/introduction/overview/ +# +# This workflow creates daily repo status reports. It gathers recent repository +# activity (issues, PRs, discussions, releases, code changes) and generates +# engaging GitHub issues with productivity insights, community highlights, +# and project recommendations. +# +# Source: githubnext/agentics/workflows/repo-status.md@main +# +# Secrets used: +# - COPILOT_GITHUB_TOKEN +# - GH_AW_GITHUB_MCP_SERVER_TOKEN +# - GH_AW_GITHUB_TOKEN +# - GITHUB_TOKEN +# +# Custom actions used: +# - actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 +# - actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 +# - actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 +# - actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 +# - actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 +# - github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 +# +# Container images used: +# - ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 +# - ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3 +# - ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca +# - ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f +# - ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 +# - node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + +name: "Repo Status" +on: + schedule: + - cron: "11 19 * * *" + # Friendly format: daily (scattered) + workflow_dispatch: + inputs: + aw_context: + default: "" + description: "Agent caller context (used internally by Agentic Workflows)." + required: false + type: string + +permissions: {} + +concurrency: + group: "gh-aw-${{ github.workflow }}" + +run-name: "Repo Status" + +jobs: + activation: + runs-on: ubuntu-slim + permissions: + actions: read + contents: read + outputs: + comment_id: "" + comment_repo: "" + engine_id: ${{ steps.generate_aw_info.outputs.engine_id }} + lockdown_check_failed: ${{ steps.generate_aw_info.outputs.lockdown_check_failed == 'true' }} + model: ${{ steps.generate_aw_info.outputs.model }} + secret_verification_result: ${{ steps.validate-secret.outputs.verification_result }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + stale_lock_file_failed: ${{ steps.check-lock-file.outputs.stale_lock_file_failed == 'true' }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Generate agentic run info + id: generate_aw_info + env: + GH_AW_INFO_ENGINE_ID: "copilot" + GH_AW_INFO_ENGINE_NAME: "GitHub Copilot CLI" + GH_AW_INFO_MODEL: "claude-haiku-4.5" + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AGENT_VERSION: "1.0.52" + GH_AW_INFO_CLI_VERSION: "v0.76.1" + GH_AW_INFO_WORKFLOW_NAME: "Repo Status" + GH_AW_INFO_EXPERIMENTAL: "false" + GH_AW_INFO_SUPPORTS_TOOLS_ALLOWLIST: "true" + GH_AW_INFO_STAGED: "false" + GH_AW_INFO_ALLOWED_DOMAINS: '["defaults"]' + GH_AW_INFO_FIREWALL_ENABLED: "true" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_AWMG_VERSION: "" + GH_AW_INFO_FIREWALL_TYPE: "squid" + GH_AW_INFO_FRONTMATTER_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_COMPILED_STRICT: "true" + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_aw_info.cjs'); + await main(core, context); + - name: Validate COPILOT_GITHUB_TOKEN secret + id: validate-secret + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_multi_secret.sh" COPILOT_GITHUB_TOKEN 'GitHub Copilot CLI' https://github.github.com/gh-aw/reference/engines/#github-copilot-default + env: + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + - name: Checkout .github and .agents folders + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + sparse-checkout: | + .github + .agents + .antigravity + .claude + .codex + .crush + .gemini + .opencode + .pi + sparse-checkout-cone-mode: true + fetch-depth: 1 + - name: Save agent config folders for base branch restoration + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/save_base_github_folders.sh" + - name: Check workflow lock file + id: check-lock-file + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_WORKFLOW_FILE: "daily-repo-status.lock.yml" + GH_AW_CONTEXT_WORKFLOW_REF: "${{ github.workflow_ref }}" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_workflow_timestamp_api.cjs'); + await main(); + - name: Check compile-agentic version + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_COMPILED_VERSION: "v0.76.1" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/check_version_updates.cjs'); + await main(); + - name: Create prompt with built-in context + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ runner.temp }}/gh-aw/safeoutputs/outputs.jsonl + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + # poutine:ignore untrusted_checkout_exec + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/create_prompt_first.sh" + { + cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + + GH_AW_PROMPT_eeb322738661ed58_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/xpia.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/temp_folder_prompt.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/markdown.md" + cat "${RUNNER_TEMP}/gh-aw/prompts/safe_outputs_prompt.md" + cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + + Tools: create_issue, missing_tool, missing_data, noop + + GH_AW_PROMPT_eeb322738661ed58_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/mcp_cli_tools_prompt.md" + cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + + The following GitHub context information is available for this workflow: + {{#if github.actor}} + - **actor**: __GH_AW_GITHUB_ACTOR__ + {{/if}} + {{#if github.repository}} + - **repository**: __GH_AW_GITHUB_REPOSITORY__ + {{/if}} + {{#if github.workspace}} + - **workspace**: __GH_AW_GITHUB_WORKSPACE__ + {{/if}} + {{#if github.event.issue.number || (github.aw.context.item_type == 'issue' && github.aw.context.item_number)}} + - **issue-number**: #__GH_AW_EXPR_802A9F6A__ + {{/if}} + {{#if github.event.discussion.number || (github.aw.context.item_type == 'discussion' && github.aw.context.item_number)}} + - **discussion-number**: #__GH_AW_EXPR_1A3A194A__ + {{/if}} + {{#if github.event.pull_request.number || (github.aw.context.item_type == 'pull_request' && github.aw.context.item_number)}} + - **pull-request-number**: #__GH_AW_EXPR_463A214A__ + {{/if}} + {{#if github.event.comment.id || github.aw.context.comment_id}} + - **comment-id**: __GH_AW_EXPR_FF1D34CE__ + {{/if}} + {{#if github.run_id}} + - **workflow-run-id**: __GH_AW_GITHUB_RUN_ID__ + {{/if}} + + + GH_AW_PROMPT_eeb322738661ed58_EOF + cat "${RUNNER_TEMP}/gh-aw/prompts/github_mcp_tools_with_safeoutputs_prompt.md" + cat << 'GH_AW_PROMPT_eeb322738661ed58_EOF' + + {{#runtime-import .github/workflows/daily-repo-status.md}} + GH_AW_PROMPT_eeb322738661ed58_EOF + } > "$GH_AW_PROMPT" + - name: Interpolate variables and render templates + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_ENGINE_ID: "copilot" + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/interpolate_prompt.cjs'); + await main(); + - name: Substitute placeholders + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_EXPR_1A3A194A: ${{ github.event.discussion.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'discussion' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_463A214A: ${{ github.event.pull_request.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'pull_request' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_802A9F6A: ${{ github.event.issue.number || (fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_type == 'issue' && fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').item_number) }} + GH_AW_EXPR_FF1D34CE: ${{ github.event.comment.id || fromJSON(github.event.inputs.aw_context || github.event.client_payload.aw_context || '{}').comment_id }} + GH_AW_GITHUB_ACTOR: ${{ github.actor }} + GH_AW_GITHUB_REPOSITORY: ${{ github.repository }} + GH_AW_GITHUB_RUN_ID: ${{ github.run_id }} + GH_AW_GITHUB_WORKSPACE: ${{ github.workspace }} + GH_AW_MCP_CLI_SERVERS_LIST: '- `safeoutputs` — run `safeoutputs --help` to see available tools' + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + + const substitutePlaceholders = require('${{ runner.temp }}/gh-aw/actions/substitute_placeholders.cjs'); + + // Call the substitution function + return await substitutePlaceholders({ + file: process.env.GH_AW_PROMPT, + substitutions: { + GH_AW_EXPR_1A3A194A: process.env.GH_AW_EXPR_1A3A194A, + GH_AW_EXPR_463A214A: process.env.GH_AW_EXPR_463A214A, + GH_AW_EXPR_802A9F6A: process.env.GH_AW_EXPR_802A9F6A, + GH_AW_EXPR_FF1D34CE: process.env.GH_AW_EXPR_FF1D34CE, + GH_AW_GITHUB_ACTOR: process.env.GH_AW_GITHUB_ACTOR, + GH_AW_GITHUB_REPOSITORY: process.env.GH_AW_GITHUB_REPOSITORY, + GH_AW_GITHUB_RUN_ID: process.env.GH_AW_GITHUB_RUN_ID, + GH_AW_GITHUB_WORKSPACE: process.env.GH_AW_GITHUB_WORKSPACE, + GH_AW_MCP_CLI_SERVERS_LIST: process.env.GH_AW_MCP_CLI_SERVERS_LIST + } + }); + - name: Validate prompt placeholders + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/validate_prompt_placeholders.sh" + - name: Print prompt + env: + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + # poutine:ignore untrusted_checkout_exec + run: bash "${RUNNER_TEMP}/gh-aw/actions/print_prompt_summary.sh" + - name: Upload activation artifact + if: success() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: activation + include-hidden-files: true + path: | + /tmp/gh-aw/aw_info.json + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/aw-prompts/prompt-template.txt + /tmp/gh-aw/aw-prompts/prompt-import-tree.json + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/base + /tmp/gh-aw/.github/agents + /tmp/gh-aw/.github/skills + if-no-files-found: ignore + retention-days: 1 + + agent: + needs: activation + runs-on: ubuntu-latest + permissions: + contents: read + issues: read + pull-requests: read + concurrency: + group: "gh-aw-copilot-${{ github.workflow }}" + env: + DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} + GH_AW_ASSETS_ALLOWED_EXTS: "" + GH_AW_ASSETS_BRANCH: "" + GH_AW_ASSETS_MAX_SIZE_KB: 0 + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + GH_AW_WORKFLOW_ID_SANITIZED: dailyrepostatus + outputs: + agentic_engine_timeout: ${{ steps.detect-agent-errors.outputs.agentic_engine_timeout || 'false' }} + checkout_pr_success: ${{ steps.checkout-pr.outputs.checkout_pr_success || 'true' }} + effective_tokens: ${{ steps.parse-mcp-gateway.outputs.effective_tokens }} + effective_tokens_rate_limit_error: ${{ steps.parse-mcp-gateway.outputs.effective_tokens_rate_limit_error || 'false' }} + has_patch: ${{ steps.collect_output.outputs.has_patch }} + inference_access_error: ${{ steps.detect-agent-errors.outputs.inference_access_error || 'false' }} + mcp_policy_error: ${{ steps.detect-agent-errors.outputs.mcp_policy_error || 'false' }} + model: ${{ needs.activation.outputs.model }} + model_not_supported_error: ${{ steps.detect-agent-errors.outputs.model_not_supported_error || 'false' }} + output: ${{ steps.collect_output.outputs.output }} + output_types: ${{ steps.collect_output.outputs.output_types }} + setup-parent-span-id: ${{ steps.setup.outputs.parent-span-id || steps.setup.outputs.span-id }} + setup-span-id: ${{ steps.setup.outputs.span-id }} + setup-trace-id: ${{ steps.setup.outputs.trace-id }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Set runtime paths + id: set-runtime-paths + run: | + { + echo "GH_AW_SAFE_OUTPUTS=${RUNNER_TEMP}/gh-aw/safeoutputs/outputs.jsonl" + echo "GH_AW_SAFE_OUTPUTS_CONFIG_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" + echo "GH_AW_SAFE_OUTPUTS_TOOLS_PATH=${RUNNER_TEMP}/gh-aw/safeoutputs/tools.json" + } >> "$GITHUB_OUTPUT" + - name: Checkout repository + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + - name: Create gh-aw temp directory + run: bash "${RUNNER_TEMP}/gh-aw/actions/create_gh_aw_tmp_dir.sh" + - name: Configure gh CLI for GitHub Enterprise + run: bash "${RUNNER_TEMP}/gh-aw/actions/configure_gh_for_ghe.sh" + env: + GH_TOKEN: ${{ github.token }} + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Checkout PR branch + id: checkout-pr + if: | + github.event.pull_request || github.event.issue.pull_request + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/checkout_pr_branch.cjs'); + await main(); + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 + - name: Parse integrity filter lists + id: parse-guard-vars + env: + GH_AW_BLOCKED_USERS_VAR: ${{ vars.GH_AW_GITHUB_BLOCKED_USERS || '' }} + GH_AW_TRUSTED_USERS_VAR: ${{ vars.GH_AW_GITHUB_TRUSTED_USERS || '' }} + GH_AW_APPROVAL_LABELS_VAR: ${{ vars.GH_AW_GITHUB_APPROVAL_LABELS || '' }} + run: bash "${RUNNER_TEMP}/gh-aw/actions/parse_guard_list.sh" + - name: Download activation artifact + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: activation + path: /tmp/gh-aw + - name: Restore agent config folders from base branch + if: steps.checkout-pr.outcome == 'success' + env: + GH_AW_AGENT_FOLDERS: ".agents .antigravity .claude .codex .crush .gemini .github .opencode .pi" + GH_AW_AGENT_FILES: ".crush.json AGENTS.md ANTIGRAVITY.md CLAUDE.md GEMINI.md PI.md opencode.jsonc" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_base_github_folders.sh" + - name: Restore inline sub-agents from activation artifact + env: + GH_AW_SUB_AGENT_DIR: ".github/agents" + GH_AW_SUB_AGENT_EXT: ".agent.md" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_sub_agents.sh" + - name: Restore inline skills from activation artifact + env: + GH_AW_SKILL_DIR: ".github/skills" + run: bash "${RUNNER_TEMP}/gh-aw/actions/restore_inline_skills.sh" + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3 ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca ghcr.io/github/gh-aw-mcpg:v0.3.19@sha256:a6c890d7c24d7190c9ef97b9c954cc4cffaae6b01c371ced1f959f1370b1f68f ghcr.io/github/github-mcp-server:v1.0.4@sha256:e3816a476a977cfb836e7d221510011436c654d11861db66ecfd826601aba6a4 node:lts-alpine@sha256:2bdb65ed1dab192432bc31c95f94155ca5ad7fc1392fb7eb7526ab682fa5bf14 + - name: Generate Safe Outputs Config + run: | + mkdir -p "${RUNNER_TEMP}/gh-aw/safeoutputs" + mkdir -p /tmp/gh-aw/safeoutputs + mkdir -p /tmp/gh-aw/mcp-logs/safeoutputs + cat > "${RUNNER_TEMP}/gh-aw/safeoutputs/config.json" << 'GH_AW_SAFE_OUTPUTS_CONFIG_51571b44da85874d_EOF' + {"create_issue":{"close_older_issues":true,"labels":["report","daily-status"],"max":1,"title_prefix":"[repo-status] "},"create_report_incomplete_issue":{},"mentions":{"enabled":false},"missing_data":{},"missing_tool":{},"noop":{"max":1,"report-as-issue":"true"},"report_incomplete":{}} + GH_AW_SAFE_OUTPUTS_CONFIG_51571b44da85874d_EOF + - name: Generate Safe Outputs Tools + env: + GH_AW_TOOLS_META_JSON: | + { + "description_suffixes": { + "create_issue": " CONSTRAINTS: Maximum 1 issue(s) can be created. Title will be prefixed with \"[repo-status] \". Labels [\"report\" \"daily-status\"] will be automatically added." + }, + "repo_params": {}, + "dynamic_tools": [] + } + GH_AW_VALIDATION_JSON: | + { + "create_issue": { + "defaultMax": 1, + "fields": { + "body": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "fields": { + "type": "array" + }, + "labels": { + "type": "array", + "itemType": "string", + "itemSanitize": true, + "itemMaxLength": 128 + }, + "parent": { + "issueOrPRNumber": true + }, + "repo": { + "type": "string", + "maxLength": 256 + }, + "temporary_id": { + "type": "string" + }, + "title": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "missing_data": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "context": { + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "data_type": { + "type": "string", + "sanitize": true, + "maxLength": 128 + }, + "reason": { + "type": "string", + "sanitize": true, + "maxLength": 256 + } + } + }, + "missing_tool": { + "defaultMax": 20, + "fields": { + "alternatives": { + "type": "string", + "sanitize": true, + "maxLength": 512 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 256 + }, + "tool": { + "type": "string", + "sanitize": true, + "maxLength": 128 + } + } + }, + "noop": { + "defaultMax": 1, + "fields": { + "message": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 65000 + } + } + }, + "report_incomplete": { + "defaultMax": 5, + "fields": { + "details": { + "type": "string", + "sanitize": true, + "maxLength": 65000 + }, + "reason": { + "required": true, + "type": "string", + "sanitize": true, + "maxLength": 1024 + } + } + } + } + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/generate_safe_outputs_tools.cjs'); + await main(); + - name: Generate Safe Outputs MCP Server Config + id: safe-outputs-config + run: | + # Generate a secure random API key (360 bits of entropy, 40+ chars) + # Mask immediately to prevent timing vulnerabilities + API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${API_KEY}" + + PORT=3001 + + # Set outputs for next steps + { + echo "safe_outputs_api_key=${API_KEY}" + echo "safe_outputs_port=${PORT}" + } >> "$GITHUB_OUTPUT" + + echo "Safe Outputs MCP server will run on port ${PORT}" + + - name: Start Safe Outputs MCP HTTP Server + id: safe-outputs-start + env: + DEBUG: '*' + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-config.outputs.safe_outputs_port }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-config.outputs.safe_outputs_api_key }} + GH_AW_SAFE_OUTPUTS_TOOLS_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/tools.json + GH_AW_SAFE_OUTPUTS_CONFIG_PATH: ${{ runner.temp }}/gh-aw/safeoutputs/config.json + GH_AW_MCP_LOG_DIR: /tmp/gh-aw/mcp-logs/safeoutputs + run: | + # Environment variables are set above to prevent template injection + export DEBUG + export GH_AW_SAFE_OUTPUTS + export GH_AW_SAFE_OUTPUTS_PORT + export GH_AW_SAFE_OUTPUTS_API_KEY + export GH_AW_SAFE_OUTPUTS_TOOLS_PATH + export GH_AW_SAFE_OUTPUTS_CONFIG_PATH + export GH_AW_MCP_LOG_DIR + + bash "${RUNNER_TEMP}/gh-aw/actions/start_safe_outputs_server.sh" + + - name: Start MCP Gateway + id: start-mcp-gateway + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_SAFE_OUTPUTS_API_KEY: ${{ steps.safe-outputs-start.outputs.api_key }} + GH_AW_SAFE_OUTPUTS_PORT: ${{ steps.safe-outputs-start.outputs.port }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + run: | + set -eo pipefail + mkdir -p "${RUNNER_TEMP}/gh-aw/mcp-config" + + # Export gateway environment variables for MCP config and gateway script + export MCP_GATEWAY_PORT="8080" + export MCP_GATEWAY_DOMAIN="host.docker.internal" + export MCP_GATEWAY_HOST_DOMAIN="localhost" + MCP_GATEWAY_API_KEY=$(openssl rand -base64 45 | tr -d '/+=') + echo "::add-mask::${MCP_GATEWAY_API_KEY}" + export MCP_GATEWAY_API_KEY + export MCP_GATEWAY_PAYLOAD_DIR="/tmp/gh-aw/mcp-payloads" + mkdir -p "${MCP_GATEWAY_PAYLOAD_DIR}" + export MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD="524288" + export DEBUG="*" + + export GH_AW_ENGINE="copilot" + MCP_GATEWAY_UID=$(id -u 2>/dev/null || echo '0') + MCP_GATEWAY_GID=$(id -g 2>/dev/null || echo '0') + case "${DOCKER_HOST:-}" in + unix://* ) DOCKER_SOCK_PATH="${DOCKER_HOST#unix://}" ;; + /* ) DOCKER_SOCK_PATH="$DOCKER_HOST" ;; + * ) DOCKER_SOCK_PATH=/var/run/docker.sock ;; + esac + DOCKER_SOCK_GID=$(stat -c '%g' "$DOCKER_SOCK_PATH" 2>/dev/null || echo '0') + export MCP_GATEWAY_DOCKER_COMMAND='docker run -i --rm --network host --add-host host.docker.internal:127.0.0.1 --user '"${MCP_GATEWAY_UID}"':'"${MCP_GATEWAY_GID}"' --group-add '"${DOCKER_SOCK_GID}"' -v '"${DOCKER_SOCK_PATH}"':/var/run/docker.sock -e MCP_GATEWAY_PORT -e MCP_GATEWAY_DOMAIN -e MCP_GATEWAY_API_KEY -e MCP_GATEWAY_PAYLOAD_DIR -e MCP_GATEWAY_PAYLOAD_SIZE_THRESHOLD -e DOCKER_HOST=unix:///var/run/docker.sock -e DEBUG -e MCP_GATEWAY_LOG_DIR -e GH_AW_MCP_LOG_DIR -e GH_AW_SAFE_OUTPUTS -e GH_AW_SAFE_OUTPUTS_CONFIG_PATH -e GH_AW_SAFE_OUTPUTS_TOOLS_PATH -e GH_AW_ASSETS_BRANCH -e GH_AW_ASSETS_MAX_SIZE_KB -e GH_AW_ASSETS_ALLOWED_EXTS -e DEFAULT_BRANCH -e GITHUB_MCP_SERVER_TOKEN -e GITHUB_MCP_GUARD_MIN_INTEGRITY -e GITHUB_MCP_GUARD_REPOS -e GITHUB_REPOSITORY -e GITHUB_SERVER_URL -e GITHUB_SHA -e GITHUB_WORKSPACE -e GITHUB_TOKEN -e GITHUB_RUN_ID -e GITHUB_RUN_NUMBER -e GITHUB_RUN_ATTEMPT -e GITHUB_JOB -e GITHUB_ACTION -e GITHUB_EVENT_NAME -e GITHUB_EVENT_PATH -e GITHUB_ACTOR -e GITHUB_ACTOR_ID -e GITHUB_TRIGGERING_ACTOR -e GITHUB_WORKFLOW -e GITHUB_WORKFLOW_REF -e GITHUB_WORKFLOW_SHA -e GITHUB_REF -e GITHUB_REF_NAME -e GITHUB_REF_TYPE -e GITHUB_HEAD_REF -e GITHUB_BASE_REF -e GH_AW_SAFE_OUTPUTS_PORT -e GH_AW_SAFE_OUTPUTS_API_KEY -v /tmp/gh-aw/mcp-payloads:/tmp/gh-aw/mcp-payloads:rw -v /opt:/opt:ro -v /tmp:/tmp:rw -v '"${GITHUB_WORKSPACE}"':'"${GITHUB_WORKSPACE}"':rw ghcr.io/github/gh-aw-mcpg:v0.3.19' + + mkdir -p /home/runner/.copilot + GH_AW_NODE=$(which node 2>/dev/null || command -v node 2>/dev/null || echo node) + cat << GH_AW_MCP_CONFIG_4b3a7789a6eea081_EOF | "$GH_AW_NODE" "${RUNNER_TEMP}/gh-aw/actions/start_mcp_gateway.cjs" + { + "mcpServers": { + "github": { + "type": "stdio", + "container": "ghcr.io/github/github-mcp-server:v1.0.4", + "env": { + "GITHUB_HOST": "\${GITHUB_SERVER_URL}", + "GITHUB_PERSONAL_ACCESS_TOKEN": "\${GITHUB_MCP_SERVER_TOKEN}", + "GITHUB_READ_ONLY": "1", + "GITHUB_TOOLSETS": "context,repos,issues,pull_requests" + }, + "guard-policies": { + "allow-only": { + "approval-labels": ${{ steps.parse-guard-vars.outputs.approval_labels }}, + "blocked-users": ${{ steps.parse-guard-vars.outputs.blocked_users }}, + "min-integrity": "none", + "repos": "all", + "trusted-users": ${{ steps.parse-guard-vars.outputs.trusted_users }} + } + } + }, + "safeoutputs": { + "type": "http", + "url": "http://host.docker.internal:$GH_AW_SAFE_OUTPUTS_PORT", + "headers": { + "Authorization": "\${GH_AW_SAFE_OUTPUTS_API_KEY}" + }, + "guard-policies": { + "write-sink": { + "accept": [ + "*" + ] + } + } + } + }, + "gateway": { + "port": $MCP_GATEWAY_PORT, + "domain": "${MCP_GATEWAY_DOMAIN}", + "apiKey": "${MCP_GATEWAY_API_KEY}", + "payloadDir": "${MCP_GATEWAY_PAYLOAD_DIR}" + } + } + GH_AW_MCP_CONFIG_4b3a7789a6eea081_EOF + - name: Mount MCP servers as CLIs + id: mount-mcp-clis + continue-on-error: true + env: + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + MCP_GATEWAY_DOMAIN: ${{ steps.start-mcp-gateway.outputs.gateway-domain }} + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io); + const { main } = require('${{ runner.temp }}/gh-aw/actions/mount_mcp_as_cli.cjs'); + await main(); + - name: Clean credentials + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/clean_git_credentials.sh" + - name: Audit pre-agent workspace + id: pre_agent_audit + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/audit_pre_agent_workspace.sh" + - name: Execute GitHub Copilot CLI + id: agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/agent-stdio.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["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","www.googleapis.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000,"models":{"agent":["sonnet-6x","gpt-5.4","gpt-5.3","gemini-pro","any"],"antigravity":["copilot/antigravity*","google/antigravity*","gemini/antigravity*"],"any":["copilot/*","anthropic/*","openai/*","google/*","gemini/*"],"claude":["agent"],"codex":["agent"],"coding":["copilot/gpt-5*codex*","openai/gpt-5*codex*","gpt-5-codex"],"computer-use":["copilot/*computer-use*","google/*computer-use*","gemini/*computer-use*","openai/*computer-use*"],"copilot":["agent"],"deep-research":["copilot/deep-research*","copilot/o3-deep-research*","copilot/o4-mini-deep-research*","google/deep-research*","gemini/deep-research*","openai/o3-deep-research*","openai/o4-mini-deep-research*"],"gemini":["agent"],"gemini-3-flash":["copilot/gemini-3*flash*","google/gemini-3*flash*","gemini/gemini-3*flash*"],"gemini-3-pro":["copilot/gemini-3*pro*","google/gemini-3*pro*","gemini/gemini-3*pro*"],"gemini-3.1-flash":["copilot/gemini-3.1*flash*","google/gemini-3.1*flash*","gemini/gemini-3.1*flash*"],"gemini-3.1-pro":["copilot/gemini-3.1*pro*","google/gemini-3.1*pro*","gemini/gemini-3.1*pro*"],"gemini-3.5-flash":["copilot/gemini-3.5*flash*","google/gemini-3.5*flash*","gemini/gemini-3.5*flash*"],"gemini-flash":["copilot/gemini-*flash*","google/gemini-*flash*","gemini/gemini-*flash*"],"gemini-flash-lite":["copilot/gemini-*flash*lite*","google/gemini-*flash*lite*","gemini/gemini-*flash*lite*"],"gemini-pro":["copilot/gemini-*pro*","google/gemini-*pro*","gemini/gemini-*pro*"],"gemma":["copilot/gemma*","google/gemma*","gemini/gemma*"],"gpt-4.1":["copilot/gpt-4.1*","openai/gpt-4.1*"],"gpt-5":["copilot/gpt-5*","openai/gpt-5*"],"gpt-5-codex":["copilot/gpt-5*codex*","openai/gpt-5*codex*"],"gpt-5-mini":["copilot/gpt-5*mini*","openai/gpt-5*mini*"],"gpt-5-nano":["copilot/gpt-5*nano*","openai/gpt-5*nano*"],"gpt-5-pro":["copilot/gpt-5*pro*","openai/gpt-5*pro*"],"gpt-5.2":["copilot/gpt-5.2*","openai/gpt-5.2*"],"gpt-5.3":["copilot/gpt-5.3*","openai/gpt-5.3*"],"gpt-5.4":["copilot/gpt-5.4*","openai/gpt-5.4*"],"gpt-5.5":["copilot/gpt-5.5*","openai/gpt-5.5*"],"haiku":["copilot/*haiku*","anthropic/*haiku*"],"large":["sonnet","gpt-5-pro","gpt-5","gemini-pro"],"mini":["haiku","gpt-5-mini","gpt-5-nano","gemini-flash-lite"],"opus":["copilot/*opus*","anthropic/*opus*"],"opusplan":["opus?effort=high"],"reasoning":["copilot/o1*","copilot/o3*","copilot/o4*","openai/o1*","openai/o3*","openai/o4*"],"robotics":["copilot/*robotics*","google/*robotics*","gemini/*robotics*"],"small":["mini"],"sonnet":["copilot/*sonnet*","anthropic/*sonnet*"],"sonnet-6x":["copilot/*sonnet-4-5-*","anthropic/*sonnet-4-5-*","copilot/*sonnet-4-6*","anthropic/*sonnet-4-6*"],"summarization":["haiku","gpt-5-mini","gemini-flash-lite","mini"],"vision":["copilot/gemini-*image*","gemini/gemini-*image*","copilot/gemini-*flash*","gemini/gemini-*flash*"]}},"container":{"imageTag":"0.25.55,squid=sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca,agent=sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731,api-proxy=sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --exclude-env GITHUB_MCP_SERVER_TOKEN --exclude-env MCP_GATEWAY_API_KEY --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="${RUNNER_TEMP}/gh-aw/mcp-cli/bin:$PATH" && export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --allow-all-paths --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/agent-stdio.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-haiku-4.5 + GH_AW_MCP_CONFIG: /home/runner/.copilot/mcp-config.json + GH_AW_PHASE: agent + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + GH_AW_VERSION: v0.76.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN || secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Detect agent errors + if: always() + id: detect-agent-errors + continue-on-error: true + run: node "${RUNNER_TEMP}/gh-aw/actions/detect_agent_errors.cjs" + - name: Configure Git credentials + env: + REPO_NAME: ${{ github.repository }} + SERVER_URL: ${{ github.server_url }} + GITHUB_TOKEN: ${{ github.token }} + run: | + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git config --global am.keepcr true + # Re-authenticate git with GitHub token + SERVER_URL_STRIPPED="${SERVER_URL#https://}" + git remote set-url origin "https://x-access-token:${GITHUB_TOKEN}@${SERVER_URL_STRIPPED}/${REPO_NAME}.git" + echo "Git configured with standard GitHub Actions identity" + - name: Copy Copilot session state files to logs + if: always() + continue-on-error: true + run: bash "${RUNNER_TEMP}/gh-aw/actions/copy_copilot_session_state.sh" + - name: Stop MCP Gateway + if: always() + continue-on-error: true + env: + MCP_GATEWAY_PORT: ${{ steps.start-mcp-gateway.outputs.gateway-port }} + MCP_GATEWAY_API_KEY: ${{ steps.start-mcp-gateway.outputs.gateway-api-key }} + GATEWAY_PID: ${{ steps.start-mcp-gateway.outputs.gateway-pid }} + run: | + bash "${RUNNER_TEMP}/gh-aw/actions/stop_mcp_gateway.sh" "$GATEWAY_PID" + - name: Redact secrets in logs + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/redact_secrets.cjs'); + await main(); + env: + GH_AW_SECRET_NAMES: 'COPILOT_GITHUB_TOKEN,GH_AW_GITHUB_MCP_SERVER_TOKEN,GH_AW_GITHUB_TOKEN,GITHUB_TOKEN' + SECRET_COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + SECRET_GH_AW_GITHUB_MCP_SERVER_TOKEN: ${{ secrets.GH_AW_GITHUB_MCP_SERVER_TOKEN }} + SECRET_GH_AW_GITHUB_TOKEN: ${{ secrets.GH_AW_GITHUB_TOKEN }} + SECRET_GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Append agent step summary + if: always() + run: bash "${RUNNER_TEMP}/gh-aw/actions/append_agent_step_summary.sh" + - name: Copy Safe Outputs + if: always() + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.GH_AW_SAFE_OUTPUTS }} + run: | + mkdir -p /tmp/gh-aw + cp "$GH_AW_SAFE_OUTPUTS" /tmp/gh-aw/safeoutputs.jsonl 2>/dev/null || true + - name: Ingest agent output + id: collect_output + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_SAFE_OUTPUTS: ${{ steps.set-runtime-paths.outputs.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,www.googleapis.com" + GH_AW_ALLOWED_GITHUB_REFS: "" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/collect_ndjson_output.cjs'); + await main(); + - name: Parse agent logs for step summary + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: /tmp/gh-aw/sandbox/agent/logs/ + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_copilot_log.cjs'); + await main(); + - name: Parse MCP Gateway logs for step summary + if: always() + id: parse-mcp-gateway + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_mcp_gateway_log.cjs'); + await main(); + - name: Print firewall logs + if: always() + continue-on-error: true + env: + AWF_LOGS_DIR: /tmp/gh-aw/sandbox/firewall/logs + run: | + # Fix permissions on firewall logs/audit dirs so they can be uploaded as artifacts + # AWF runs with sudo, creating files owned by root + sudo chmod -R a+rX /tmp/gh-aw/sandbox/firewall 2>/dev/null || true + # Only run awf logs summary if awf command exists (it may not be installed if workflow failed before install step) + if command -v awf &> /dev/null; then + awf logs summary | tee -a "$GITHUB_STEP_SUMMARY" + else + echo 'AWF binary not installed, skipping firewall log summary' + fi + - name: Parse token usage for step summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_token_usage.cjs'); + await main(); + - name: Print AWF reflect summary + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/awf_reflect_summary.cjs'); + await main(); + - name: Write agent output placeholder if missing + if: always() + run: | + if [ ! -f /tmp/gh-aw/agent_output.json ]; then + echo '{"items":[]}' > /tmp/gh-aw/agent_output.json + fi + - name: Upload agent artifacts + if: always() + continue-on-error: true + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: agent + path: | + /tmp/gh-aw/aw-prompts/prompt.txt + /tmp/gh-aw/sandbox/agent/logs/ + /tmp/gh-aw/redacted-urls.log + /tmp/gh-aw/mcp-logs/ + /tmp/gh-aw/proxy-logs/ + !/tmp/gh-aw/proxy-logs/proxy-tls/ + /tmp/gh-aw/agent_usage.json + /tmp/gh-aw/agent-stdio.log + /tmp/gh-aw/pre-agent-audit.txt + /tmp/gh-aw/agent/ + /tmp/gh-aw/github_rate_limits.jsonl + /tmp/gh-aw/safeoutputs.jsonl + /tmp/gh-aw/agent_output.json + /tmp/gh-aw/aw-*.patch + /tmp/gh-aw/aw-*.bundle + /tmp/gh-aw/awf-config.json + /tmp/gh-aw/sandbox/firewall/logs/ + /tmp/gh-aw/sandbox/firewall/audit/ + /tmp/gh-aw/sandbox/firewall/awf-reflect.json + if-no-files-found: ignore + + conclusion: + needs: + - activation + - agent + - detection + - safe_outputs + if: > + always() && (needs.agent.result != 'skipped' || needs.activation.outputs.lockdown_check_failed == 'true' || + needs.activation.outputs.stale_lock_file_failed == 'true') + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + concurrency: + group: "gh-aw-conclusion-daily-repo-status" + cancel-in-progress: false + queue: max + outputs: + incomplete_count: ${{ steps.report_incomplete.outputs.incomplete_count }} + noop_message: ${{ steps.noop.outputs.noop_message }} + tools_reported: ${{ steps.missing_tool.outputs.tools_reported }} + total_count: ${{ steps.missing_tool.outputs.total_count }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Process no-op messages + id: noop + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_NOOP_MAX: "1" + GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_NOOP_REPORT_AS_ISSUE: "true" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_noop_message.cjs'); + await main(); + - name: Log detection run + id: detection_runs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_detection_runs.cjs'); + await main(); + - name: Record missing tool + id: missing_tool + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_MISSING_TOOL_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/missing_tool.cjs'); + await main(); + - name: Record incomplete + id: report_incomplete + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_REPORT_INCOMPLETE_CREATE_ISSUE: "true" + GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/report_incomplete_handler.cjs'); + await main(); + - name: Handle agent failure + id: handle_agent_failure + if: always() + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" + GH_AW_RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + GH_AW_AGENT_CONCLUSION: ${{ needs.agent.result }} + GH_AW_WORKFLOW_ID: "daily-repo-status" + GH_AW_ACTION_FAILURE_ISSUE_EXPIRES_HOURS: "168" + GH_AW_ENGINE_ID: "copilot" + GH_AW_SECRET_VERIFICATION_RESULT: ${{ needs.activation.outputs.secret_verification_result }} + GH_AW_CHECKOUT_PR_SUCCESS: ${{ needs.agent.outputs.checkout_pr_success }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens || '' }} + GH_AW_EFFECTIVE_TOKENS_RATE_LIMIT_ERROR: ${{ needs.agent.outputs.effective_tokens_rate_limit_error || 'false' }} + GH_AW_INFERENCE_ACCESS_ERROR: ${{ needs.agent.outputs.inference_access_error }} + GH_AW_MCP_POLICY_ERROR: ${{ needs.agent.outputs.mcp_policy_error }} + GH_AW_AGENTIC_ENGINE_TIMEOUT: ${{ needs.agent.outputs.agentic_engine_timeout }} + GH_AW_MODEL_NOT_SUPPORTED_ERROR: ${{ needs.agent.outputs.model_not_supported_error }} + GH_AW_ENGINE_API_HOSTS: "api.enterprise.githubcopilot.com,api.githubcopilot.com,api.business.githubcopilot.com,api.individual.githubcopilot.com" + GH_AW_LOCKDOWN_CHECK_FAILED: ${{ needs.activation.outputs.lockdown_check_failed }} + GH_AW_STALE_LOCK_FILE_FAILED: ${{ needs.activation.outputs.stale_lock_file_failed }} + GH_AW_GROUP_REPORTS: "false" + GH_AW_FAILURE_REPORT_AS_ISSUE: "true" + GH_AW_MISSING_TOOL_REPORT_AS_FAILURE: "true" + GH_AW_MISSING_DATA_REPORT_AS_FAILURE: "true" + GH_AW_TIMEOUT_MINUTES: "20" + GH_AW_MAX_EFFECTIVE_TOKENS: "25000000" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/handle_agent_failure.cjs'); + await main(); + + detection: + needs: + - activation + - agent + if: > + always() && needs.agent.result != 'skipped' && (needs.agent.outputs.output_types != '' || needs.agent.outputs.has_patch == 'true') + runs-on: ubuntu-latest + permissions: + contents: read + outputs: + detection_conclusion: ${{ steps.detection_conclusion.outputs.conclusion }} + detection_reason: ${{ steps.detection_conclusion.outputs.reason }} + detection_success: ${{ steps.detection_conclusion.outputs.success }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Checkout repository for patch context + if: needs.agent.outputs.has_patch == 'true' + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false + # --- Threat Detection --- + - name: Clean stale firewall files from agent artifact + run: | + rm -rf /tmp/gh-aw/sandbox/firewall/logs + rm -rf /tmp/gh-aw/sandbox/firewall/audit + - name: Download container images + run: bash "${RUNNER_TEMP}/gh-aw/actions/download_docker_images.sh" ghcr.io/github/gh-aw-firewall/agent:0.25.55@sha256:138c363411decc9a61a5af9b95e8d64c76648b00add0ba06fc7ba786f0e72731 ghcr.io/github/gh-aw-firewall/api-proxy:0.25.55@sha256:4142b873b678cd3279b98dcbe464857d56ea2f2348719b00379cdf35dd843ff3 ghcr.io/github/gh-aw-firewall/squid:0.25.55@sha256:74084b704d8d3664a363655986664d70bd9cdb4830532d0b35cd784d867aabca + - name: Check if detection needed + id: detection_guard + if: always() + env: + OUTPUT_TYPES: ${{ needs.agent.outputs.output_types }} + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + run: | + if [[ -n "$OUTPUT_TYPES" || "$HAS_PATCH" == "true" ]]; then + echo "run_detection=true" >> "$GITHUB_OUTPUT" + echo "Detection will run: output_types=$OUTPUT_TYPES, has_patch=$HAS_PATCH" + else + echo "run_detection=false" >> "$GITHUB_OUTPUT" + echo "Detection skipped: no agent outputs or patches to analyze" + fi + - name: Clear MCP Config for detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + rm -f "${RUNNER_TEMP}/gh-aw/mcp-config/mcp-servers.json" + rm -f /home/runner/.copilot/mcp-config.json + rm -f "$GITHUB_WORKSPACE/.gemini/settings.json" + - name: Prepare threat detection files + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection/aw-prompts + cp /tmp/gh-aw/aw-prompts/prompt.txt /tmp/gh-aw/threat-detection/aw-prompts/prompt.txt 2>/dev/null || true + cp /tmp/gh-aw/agent_output.json /tmp/gh-aw/threat-detection/agent_output.json 2>/dev/null || true + for f in /tmp/gh-aw/aw-*.patch; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + for f in /tmp/gh-aw/aw-*.bundle; do + [ -f "$f" ] && cp "$f" /tmp/gh-aw/threat-detection/ 2>/dev/null || true + done + echo "Prepared threat detection files:" + ls -la /tmp/gh-aw/threat-detection/ 2>/dev/null || true + - name: Setup threat detection + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + WORKFLOW_NAME: "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." + HAS_PATCH: ${{ needs.agent.outputs.has_patch }} + with: + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/setup_threat_detection.cjs'); + await main(); + - name: Ensure threat-detection directory and log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + run: | + mkdir -p /tmp/gh-aw/threat-detection + touch /tmp/gh-aw/threat-detection/detection.log + - name: Setup Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0 + with: + node-version: '24' + package-manager-cache: false + - name: Install GitHub Copilot CLI + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_copilot_cli.sh" 1.0.52 + env: + GH_HOST: github.com + - name: Install AWF binary + run: bash "${RUNNER_TEMP}/gh-aw/actions/install_awf_binary.sh" v0.25.55 + - name: Execute GitHub Copilot CLI + if: always() && steps.detection_guard.outputs.run_detection == 'true' + continue-on-error: true + id: detection_agentic_execution + # Copilot CLI tool arguments (sorted): + timeout-minutes: 20 + run: | + set -o pipefail + printf '%s' "$(date +%s%3N)" > /tmp/gh-aw/agent_cli_start_ms.txt + touch /tmp/gh-aw/agent-step-summary.md + GH_AW_NODE_BIN=$(command -v node 2>/dev/null || true) + export GH_AW_NODE_BIN + export COPILOT_API_KEY="$COPILOT_DUMMY_BYOK" + (umask 177 && touch /tmp/gh-aw/threat-detection/detection.log) + printf '%s\n' '{"$schema":"https://github.com/github/gh-aw-firewall/releases/download/v0.25.55/awf-config.schema.json","network":{"allowDomains":["api.business.githubcopilot.com","api.enterprise.githubcopilot.com","api.github.com","api.githubcopilot.com","api.individual.githubcopilot.com","github.com","host.docker.internal","telemetry.enterprise.githubcopilot.com"]},"apiProxy":{"enabled":true,"enableTokenSteering":true,"maxRuns":500,"maxEffectiveTokens":25000000},"container":{"imageTag":"0.25.55"}}' > "${RUNNER_TEMP}/gh-aw/awf-config.json" + cp "${RUNNER_TEMP}/gh-aw/awf-config.json" /tmp/gh-aw/awf-config.json + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="" + if [[ "${DOCKER_HOST:-}" =~ ^tcp:// ]]; then + GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS="--docker-host-path-prefix /tmp/gh-aw" + fi + # shellcheck disable=SC1003 + sudo -E awf --config "${RUNNER_TEMP}/gh-aw/awf-config.json" --container-workdir "${GITHUB_WORKSPACE}" --mount "${RUNNER_TEMP}/gh-aw:${RUNNER_TEMP}/gh-aw:ro" --mount "${RUNNER_TEMP}/gh-aw:/host${RUNNER_TEMP}/gh-aw:ro" ${GH_AW_DOCKER_HOST_PATH_PREFIX_ARGS} --env-all --exclude-env COPILOT_GITHUB_TOKEN --log-level info --proxy-logs-dir /tmp/gh-aw/sandbox/firewall/logs --audit-dir /tmp/gh-aw/sandbox/firewall/audit --enable-host-access --allow-host-ports 80,443,8080 --skip-pull \ + -- /bin/bash -c 'export PATH="$(find /opt/hostedtoolcache /home/runner/work/_tool -maxdepth 5 -type d -name bin 2>/dev/null | tr '\''\n'\'' '\'':'\'')$PATH"; [ -n "$GOROOT" ] && export PATH="$GOROOT/bin:$PATH" || true && GH_AW_NODE_EXEC="${GH_AW_NODE_BIN:-}"; if [ -z "$GH_AW_NODE_EXEC" ] || [ ! -x "$GH_AW_NODE_EXEC" ]; then GH_AW_NODE_EXEC="$(command -v node 2>/dev/null || true)"; fi; if [ -z "$GH_AW_NODE_EXEC" ]; then echo "node runtime missing on this runner — check runtimes.node in workflow YAML" >&2; exit 127; fi; "$GH_AW_NODE_EXEC" ${RUNNER_TEMP}/gh-aw/actions/copilot_harness.cjs /usr/local/bin/copilot --add-dir /tmp/gh-aw/ --log-level all --log-dir /tmp/gh-aw/sandbox/agent/logs/ --disable-builtin-mcps --no-ask-user --allow-all-tools --add-dir "${GITHUB_WORKSPACE}" --prompt-file /tmp/gh-aw/aw-prompts/prompt.txt' 2>&1 | tee -a /tmp/gh-aw/threat-detection/detection.log + env: + AWF_REFLECT_ENABLED: 1 + COPILOT_AGENT_RUNNER_TYPE: STANDALONE + COPILOT_DUMMY_BYOK: dummy-byok-key-for-offline-mode + COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }} + COPILOT_MODEL: claude-haiku-4.5 + GH_AW_PHASE: detection + GH_AW_PROMPT: /tmp/gh-aw/aw-prompts/prompt.txt + GH_AW_VERSION: v0.76.1 + GITHUB_API_URL: ${{ github.api_url }} + GITHUB_AW: true + GITHUB_COPILOT_INTEGRATION_ID: agentic-workflows + GITHUB_HEAD_REF: ${{ github.head_ref }} + GITHUB_REF_NAME: ${{ github.ref_name }} + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_STEP_SUMMARY: /tmp/gh-aw/agent-step-summary.md + GITHUB_WORKSPACE: ${{ github.workspace }} + GIT_AUTHOR_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_AUTHOR_NAME: github-actions[bot] + GIT_COMMITTER_EMAIL: github-actions[bot]@users.noreply.github.com + GIT_COMMITTER_NAME: github-actions[bot] + XDG_CONFIG_HOME: /home/runner + - name: Upload threat detection log + if: always() && steps.detection_guard.outputs.run_detection == 'true' + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: detection + path: /tmp/gh-aw/threat-detection/detection.log + if-no-files-found: ignore + - name: Parse and conclude threat detection + id: detection_conclusion + if: always() + continue-on-error: true + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + RUN_DETECTION: ${{ steps.detection_guard.outputs.run_detection }} + DETECTION_AGENTIC_EXECUTION_OUTCOME: ${{ steps.detection_agentic_execution.outcome }} + GH_AW_DETECTION_CONTINUE_ON_ERROR: "true" + with: + script: | + try { + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/parse_threat_detection_results.cjs'); + await main(); + } catch (loadErr) { + const continueOnError = process.env.GH_AW_DETECTION_CONTINUE_ON_ERROR !== 'false'; + const detectionExecutionFailed = process.env.DETECTION_AGENTIC_EXECUTION_OUTCOME === 'failure'; + const msg = 'ERR_SYSTEM: \u274C Unexpected error loading threat detection module: ' + (loadErr && loadErr.message ? loadErr.message : String(loadErr)); + core.error(msg); + core.setOutput('reason', 'parse_error'); + if (continueOnError && !detectionExecutionFailed) { + core.warning('\u26A0\uFE0F ' + msg); + core.setOutput('conclusion', 'warning'); + core.setOutput('success', 'false'); + } else { + core.setOutput('conclusion', 'failure'); + core.setOutput('success', 'false'); + core.setFailed(msg); + } + } + + safe_outputs: + needs: + - activation + - agent + - detection + if: (!cancelled()) && needs.agent.result != 'skipped' && needs.detection.result == 'success' + runs-on: ubuntu-slim + permissions: + contents: read + issues: write + timeout-minutes: 15 + env: + GH_AW_CALLER_WORKFLOW_ID: "${{ github.repository }}/daily-repo-status" + GH_AW_DETECTION_CONCLUSION: ${{ needs.detection.outputs.detection_conclusion }} + GH_AW_DETECTION_REASON: ${{ needs.detection.outputs.detection_reason }} + GH_AW_EFFECTIVE_TOKENS: ${{ needs.agent.outputs.effective_tokens }} + GH_AW_ENGINE_ID: "copilot" + GH_AW_ENGINE_MODEL: "claude-haiku-4.5" + GH_AW_ENGINE_VERSION: "1.0.52" + GH_AW_WORKFLOW_ID: "daily-repo-status" + GH_AW_WORKFLOW_NAME: "Repo Status" + GH_AW_WORKFLOW_SOURCE: "githubnext/agentics/workflows/repo-status.md@main" + GH_AW_WORKFLOW_SOURCE_URL: "${{ github.server_url }}/githubnext/agentics/blob/main/workflows/repo-status.md" + outputs: + code_push_failure_count: ${{ steps.process_safe_outputs.outputs.code_push_failure_count }} + code_push_failure_errors: ${{ steps.process_safe_outputs.outputs.code_push_failure_errors }} + create_discussion_error_count: ${{ steps.process_safe_outputs.outputs.create_discussion_error_count }} + create_discussion_errors: ${{ steps.process_safe_outputs.outputs.create_discussion_errors }} + created_issue_number: ${{ steps.process_safe_outputs.outputs.created_issue_number }} + created_issue_url: ${{ steps.process_safe_outputs.outputs.created_issue_url }} + process_safe_outputs_processed_count: ${{ steps.process_safe_outputs.outputs.processed_count }} + process_safe_outputs_temporary_id_map: ${{ steps.process_safe_outputs.outputs.temporary_id_map }} + steps: + - name: Setup Scripts + id: setup + uses: github/gh-aw-actions/setup@c0338fef4749d08c21f8f975fb0e37efa17dda47 # v0.79.8 + with: + destination: ${{ runner.temp }}/gh-aw/actions + job-name: ${{ github.job }} + trace-id: ${{ needs.activation.outputs.setup-trace-id }} + parent-span-id: ${{ needs.activation.outputs.setup-parent-span-id || needs.activation.outputs.setup-span-id }} + env: + GH_AW_SETUP_WORKFLOW_NAME: "Repo Status" + GH_AW_CURRENT_WORKFLOW_REF: ${{ github.repository }}/.github/workflows/daily-repo-status.lock.yml@${{ github.ref }} + GH_AW_INFO_VERSION: "1.0.52" + GH_AW_INFO_AWF_VERSION: "v0.25.55" + GH_AW_INFO_BODY_MODIFIED: "false" + GH_AW_INFO_ENGINE_ID: "copilot" + - name: Download agent output artifact + id: download-agent-output + continue-on-error: true + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + name: agent + path: /tmp/gh-aw/ + - name: Setup agent output environment variable + id: setup-agent-output-env + if: steps.download-agent-output.outcome == 'success' + run: | + mkdir -p /tmp/gh-aw/ + find "/tmp/gh-aw/" -type f -print + echo "GH_AW_AGENT_OUTPUT=/tmp/gh-aw/agent_output.json" >> "$GITHUB_OUTPUT" + - name: Configure GH_HOST for enterprise compatibility + id: ghes-host-config + shell: bash + run: | + # Derive GH_HOST from GITHUB_SERVER_URL so the gh CLI targets the correct + # GitHub instance (GHES/GHEC). On github.com this is a harmless no-op. + GH_HOST="${GITHUB_SERVER_URL#https://}" + GH_HOST="${GH_HOST#http://}" + echo "GH_HOST=${GH_HOST}" >> "$GITHUB_ENV" + - name: Process Safe Outputs + id: process_safe_outputs + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + env: + GH_AW_AGENT_OUTPUT: ${{ steps.setup-agent-output-env.outputs.GH_AW_AGENT_OUTPUT }} + GH_AW_COMMENT_ID: ${{ needs.activation.outputs.comment_id }} + 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,www.googleapis.com" + GITHUB_SERVER_URL: ${{ github.server_url }} + GITHUB_API_URL: ${{ github.api_url }} + GH_AW_SAFE_OUTPUTS_HANDLER_CONFIG: "{\"create_issue\":{\"close_older_issues\":true,\"labels\":[\"report\",\"daily-status\"],\"max\":1,\"title_prefix\":\"[repo-status] \"},\"create_report_incomplete_issue\":{},\"mentions\":{\"enabled\":false},\"missing_data\":{},\"missing_tool\":{},\"noop\":{\"max\":1,\"report-as-issue\":\"true\"},\"report_incomplete\":{}}" + with: + github-token: ${{ secrets.GH_AW_GITHUB_TOKEN || secrets.GITHUB_TOKEN }} + script: | + const { setupGlobals } = require('${{ runner.temp }}/gh-aw/actions/setup_globals.cjs'); + setupGlobals(core, github, context, exec, io, getOctokit); + const { main } = require('${{ runner.temp }}/gh-aw/actions/safe_output_handler_manager.cjs'); + await main(); + - name: Upload Safe Outputs Items + if: always() + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 + with: + name: safe-outputs-items + path: | + /tmp/gh-aw/safe-output-items.jsonl + /tmp/gh-aw/temporary-id-map.json + if-no-files-found: ignore diff --git a/.github/workflows/daily-repo-status.md b/.github/workflows/daily-repo-status.md new file mode 100644 index 000000000000..49b553940b8b --- /dev/null +++ b/.github/workflows/daily-repo-status.md @@ -0,0 +1,62 @@ +--- +description: | + This workflow creates daily repo status reports. It gathers recent repository + activity (issues, PRs, discussions, releases, code changes) and generates + engaging GitHub issues with productivity insights, community highlights, + and project recommendations. + +on: + schedule: daily + workflow_dispatch: + +permissions: + contents: read + issues: read + pull-requests: read + +network: defaults + +engine: + id: copilot + model: claude-haiku-4.5 + +tools: + github: + # If in a public repo, setting `lockdown: false` allows + # reading issues, pull requests and comments from 3rd-parties + # If in a private repo this has no particular effect. + lockdown: false + min-integrity: none # This workflow is allowed to examine and comment on any issues + +safe-outputs: + mentions: false + allowed-github-references: [] + create-issue: + title-prefix: "[repo-status] " + labels: [report, daily-status] + close-older-issues: true +source: githubnext/agentics/workflows/repo-status.md@main +--- + +# Repo Status + +Create an upbeat daily status report for the repo as a GitHub issue. + +## What to include + +- Recent repository activity (issues, PRs, discussions, releases, code changes) +- Progress tracking, goal reminders and highlights +- Project status and recommendations +- Actionable next steps for maintainers + +## Style + +- Be positive, encouraging, and helpful 🌟 +- Use emojis moderately for engagement +- Keep it concise - adjust length based on actual activity + +## Process + +1. Gather recent activity from the repository +2. Study the repository, its issues and its pull requests +3. Create a new GitHub issue with your findings and insights diff --git a/.github/workflows/docker-cloudstack-simulator.yml b/.github/workflows/docker-cloudstack-simulator.yml index 21a09d04e0b3..ac778a48af54 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@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.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@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + 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/license-templates/LICENSE.txt b/.github/workflows/license-templates/LICENSE.txt new file mode 100644 index 000000000000..60b675e31016 --- /dev/null +++ b/.github/workflows/license-templates/LICENSE.txt @@ -0,0 +1,16 @@ +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. diff --git a/.github/workflows/main-sonar-check.yml b/.github/workflows/main-sonar-check.yml index dccd7174e54b..f48409fbc0c3 100644 --- a/.github/workflows/main-sonar-check.yml +++ b/.github/workflows/main-sonar-check.yml @@ -15,54 +15,53 @@ # 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@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: fetch-depth: 0 - - - name: Set up JDK17 - uses: actions/setup-java@v4 + 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@v4 + 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@v4 - 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@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + files: ./client/target/site/jacoco-aggregate/jacoco.xml + fail_ci_if_error: true + flags: unittests + verbose: true + name: codecov + token: ${{ secrets.CODECOV_TOKEN }} + - name: Compute Coverage Grade + run: bash scripts/coverage-grade.sh client/target/site/jacoco-aggregate/jacoco.xml diff --git a/.github/workflows/merge-conflict-checker.yml b/.github/workflows/merge-conflict-checker.yml index a997cb94ccc0..badf8c4b4c5b 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@0273be72a0bbd58fcd71d0d6c02c209b50d1e5e1 # v3.1.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." diff --git a/.github/workflows/linter.yml b/.github/workflows/pre-commit.yml similarity index 65% rename from .github/workflows/linter.yml rename to .github/workflows/pre-commit.yml index b6c814a36f4c..4de995479c59 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/pre-commit.yml @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -name: Lint +name: pre-commit on: [pull_request] @@ -29,19 +29,27 @@ 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@v4 + uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + 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@v4 + - 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') }} - name: Run pre-commit - run: pre-commit run --all-files + run: pre-commit run --color=always --all-files + - name: Run manual pre-commit hooks + run: pre-commit run --color=always --all-files --hook-stage manual diff --git a/.github/workflows/rat.yml b/.github/workflows/rat.yml index 52ce343841ba..b75ff42b9122 100644 --- a/.github/workflows/rat.yml +++ b/.github/workflows/rat.yml @@ -16,32 +16,31 @@ # under the License. name: License Check - -on: [push, pull_request] - +on: + push: + branches: + - main + - 4.22 + - 4.20 + 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@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 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 d31f55980a8f..fbb3cb9f540d 100644 --- a/.github/workflows/sonar-check.yml +++ b/.github/workflows/sonar-check.yml @@ -16,58 +16,100 @@ # 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@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 with: - ref: "refs/pull/${{ github.event.number }}/merge" fetch-depth: 0 - - - name: Set up JDK17 - uses: actions/setup-java@v4 + 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@v4 + 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@v4 - 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@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 + with: + files: ./client/target/site/jacoco-aggregate/jacoco.xml + fail_ci_if_error: true + flags: unittests + verbose: true + name: codecov + token: ${{ secrets.CODECOV_TOKEN }} + - name: Compute Coverage Grade + id: grade + run: bash scripts/coverage-grade.sh client/target/site/jacoco-aggregate/jacoco.xml + - name: Post Coverage Grade Comment on PR + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const grade = '${{ steps.grade.outputs.coverage_grade }}'; + const label = '${{ steps.grade.outputs.coverage_grade_label }}'; + const linePct = '${{ steps.grade.outputs.line_coverage }}'; + const branchPct = '${{ steps.grade.outputs.branch_coverage }}'; + const emojiMap = { A: '🟢', B: '🟡', C: '🟠', D: '🔴', F: '⛔' }; + const emoji = emojiMap[grade] ?? '❓'; + const runUrl = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`; + + const branchRow = branchPct !== 'N/A' + ? `| Branch coverage | **${branchPct}%** |` + : ''; + + const body = [ + `## ${emoji} Test Coverage Grade: \`${grade}\` — ${label}`, + '', + '| Metric | Value |', + '|--------|-------|', + `| Line coverage | **${linePct}%** |`, + branchRow, + '', + '### Grade Scale', + '| Grade | Line Coverage | Meaning |', + '|-------|--------------|---------|', + '| 🟢 A | ≥ 80% | Excellent - this code sleeps well at night 😴 |', + '| 🟡 B | 60-79% | Good - almost there, don\'t stop now 😉 |', + '| 🟠 C | 40-59% | Acceptable - your code is wearing a seatbelt, but no airbags 😬 |', + '| 🔴 D | 20-39% | Marginal - boldly shipping where no test has gone before 🖖 |', + '| ⛔ F | < 20% | Failing - tests? what tests? 🔥 |', + '', + '> Branch coverage is shown as a secondary signal. Grade is determined by **line coverage**.', + `> [View full Actions run](${runUrl})`, + ].filter(l => l !== undefined).join('\n'); + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: body, + }); + console.log('Posted coverage grade comment'); diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 000000000000..c0da5f98cc8e --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,49 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +name: 'Close stale issues and PRs' +on: + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + permissions: + actions: write + issues: write + pull-requests: write + steps: + - 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.' + close-issue-message: 'This issue was closed because it has been stale for 120 days with no activity.' + close-pr-message: 'This PR was closed because it has been stale for 240 days with no activity.' + stale-issue-label: 'no-issue-activity' + stale-pr-label: 'no-pr-activity' + days-before-stale: 120 + days-before-close: -1 + 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@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0 + with: + stale-issue-label: 'archive' + days-before-stale: 240 + exempt-issue-labels: 'gsoc,good-first-issue,long-term-plan' + days-before-close: -1 diff --git a/.github/workflows/ui.yml b/.github/workflows/ui.yml index 88d4a70e4c24..7f23add660f0 100644 --- a/.github/workflows/ui.yml +++ b/.github/workflows/ui.yml @@ -17,7 +17,13 @@ name: UI Build -on: [push, pull_request] +on: + push: + branches: + - main + - 4.22 + - 4.20 + pull_request: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} @@ -28,15 +34,19 @@ permissions: jobs: build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 + with: + persist-credentials: false - name: Set up Node - uses: actions/setup-node@v3 + 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 +65,7 @@ jobs: npm run lint npm run test:unit - - uses: codecov/codecov-action@v4 + - uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0 if: github.repository == 'apache/cloudstack' with: working-directory: ui diff --git a/.gitignore b/.gitignore index 648b93b06965..abaef83e4555 100644 --- a/.gitignore +++ b/.gitignore @@ -5,9 +5,9 @@ # 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 diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 000000000000..9fc9e21de772 --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +CHANGES.md diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e8f3c6093a9a..91537e25267e 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,23 +15,122 @@ # specific language governing permissions and limitations # under the License. --- -default_stages: [commit, push] +default_stages: [pre-commit, pre-push] default_language_version: # force all unspecified Python hooks to run python3 python: python3 -minimum_pre_commit_version: "2.17.0" +minimum_pre_commit_version: "3.2.0" repos: - repo: meta hooks: - id: identity - id: check-hooks-apply + - repo: https://github.com/thlorenz/doctoc.git + rev: v2.2.0 + hooks: + - id: doctoc + name: Add TOC for Markdown files + files: ^CONTRIBUTING\.md$|^INSTALL\.md$|^README\.md$ + - repo: https://github.com/oxipng/oxipng + rev: v9.1.5 + hooks: + - id: oxipng + name: run oxipng + description: optimize PNG images with lossless compression + args: ['-o', '4', '--strip', 'safe', '--alpha'] + - repo: https://github.com/gitleaks/gitleaks + rev: v8.27.2 + hooks: + - id: gitleaks + name: run gitleaks + description: detect hardcoded secrets + - repo: https://github.com/Lucas-C/pre-commit-hooks + rev: v1.5.5 + hooks: + - id: chmod + name: set file permissions + args: ['644'] + files: \.md$ + stages: [manual] + - id: insert-license + name: add license for all cfg files + description: automatically adds a licence header to all cfg files that don't have a license header + files: \.cfg$ + args: + - --comment-style + - '|#|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + - id: insert-license + name: add license for all Markdown files + files: \.md$ + args: + - --comment-style + - '' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + exclude: ^(CHANGES|ISSUE_TEMPLATE|PULL_REQUEST_TEMPLATE)\.md$|^ui/docs/(full|smoke)-test-plan\.template\.md$|^\.github/workflows/.*\.md$|^\.github/aw/.*\.md$ + - id: insert-license + name: add license for all properties files + description: automatically adds a licence header to all properties files that don't have a license header + files: \.properties$ + args: + - --comment-style + - '|#|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + - id: insert-license + name: add license for all Shell files + description: automatically adds a licence header to all Shell files that don't have a license header + files: \.sh$ + args: + - --comment-style + - '|#|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + - id: insert-license + name: add license for all SQL files + files: \.sql$ + args: + - --comment-style + - '|--|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + - id: insert-license + name: add license for all Vue files + files: \.vue$ + args: + - --comment-style + - '|//|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + - id: insert-license + name: add license for all YAML files + description: automatically adds a licence header to all YAML files that don't have a license header + files: \.ya?ml$ + args: + - --comment-style + - '|#|' + - --license-filepath + - .github/workflows/license-templates/LICENSE.txt + - --fuzzy-match-generates-todo + exclude: ^\.github/workflows/.*\.lock\.yml$ - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.6.0 + rev: v6.0.0 hooks: #- id: check-added-large-files - id: check-case-conflict #- id: check-executables-have-shebangs + - id: check-illegal-windows-names - id: check-merge-conflict + - id: check-shebang-scripts-are-executable + files: \.sh$ - id: check-symlinks - id: check-vcs-permalinks #- id: check-yaml @@ -42,6 +141,7 @@ repos: exclude: > (?x) ^scripts/vm/systemvm/id_rsa\.cloud$| + ^server/src/test/java/org/apache/cloudstack/network/ssl/CertServiceTest\.java$| ^server/src/test/java/com/cloud/keystore/KeystoreTest\.java$| ^server/src/test/resources/certs/dsa_self_signed\.key$| ^server/src/test/resources/certs/non_root\.key$| @@ -51,47 +151,47 @@ repos: ^server/src/test/resources/certs/rsa_self_signed\.key$| ^services/console-proxy/rdpconsole/src/test/doc/rdp-key\.pem$| ^systemvm/agent/certs/localhost\.key$| - ^systemvm/agent/certs/realhostip\.key$ + ^systemvm/agent/certs/systemvm\.key$| + ^test/integration/smoke/test_ssl_offloading\.py$ - id: end-of-file-fixer - exclude: \.vhd$ - #- id: fix-byte-order-marker + exclude: \.vhd$|\.svg$ + - id: file-contents-sorter + args: [--unique] + files: ^\.github/linters/codespell\.txt$ + - id: fix-byte-order-marker - id: forbid-submodules - id: mixed-line-ending - exclude: \.(cs|xml)$ - id: trailing-whitespace - files: \.(header|in|java|md|properties|py|rb|sh|sql|txt|vue|xml|yaml|yml)$ + files: ^(LICENSE|NOTICE)$|README$|\.(bat|cfg|config|cs|css|erb|gitignore|header|in|install|java|md|properties|py|rb|rc|sh|sql|svg|te|template|txt|ucls|vue|xml|xsl|yaml|yml)$|^cloud-cli/bindir/cloud-tool$|^debian/changelog$ args: [--markdown-linebreak-ext=md] exclude: ^services/console-proxy/rdpconsole/src/test/doc/freerdp-debug-log\.txt$ - repo: https://github.com/codespell-project/codespell - rev: v2.2.6 + rev: v2.4.2 hooks: - id: codespell name: run codespell description: Check spelling with codespell - args: [--ignore-words=.github/linters/codespell.txt] - exclude: ^ui/package\.json$|^ui/package-lock\.json$|^ui/public/js/less\.min\.js$|^ui/public/locales/.*[^n].*\.json$ - repo: https://github.com/pycqa/flake8 rev: 7.0.0 hooks: - id: flake8 args: [--config, .github/linters/.flake8] - exclude: > - (?x) - ^agent/bindir/cloud-setup-agent\.in$| - ^client/bindir/cloud-update-xenserver-licenses\.in$| - ^cloud-cli/bindir/cloud-tool$| - ^python/bindir/cloud-grab-dependent-library-versions$| - ^python/bindir/cloud-setup-baremetal$| - ^scripts/vm/hypervisor/xenserver/storagePlugin$| - ^scripts/vm/hypervisor/xenserver/vmopspremium$| - ^setup/bindir/cloud-setup-encryption\.in$| - ^venv/.*$ - repo: https://github.com/igorshubovych/markdownlint-cli - rev: v0.40.0 + rev: v0.45.0 hooks: - id: markdownlint name: run markdownlint description: check Markdown files with markdownlint args: [--config=.github/linters/.markdown-lint.yml] types: [markdown] - files: \.(md|mdown|markdown)$ + files: \.md$ + - repo: https://github.com/adrienverge/yamllint + rev: v1.37.1 + hooks: + - id: yamllint + name: run yamllint + description: check YAML files with yamllint + args: [--config-file=.github/linters/.yamllint.yml] + types: [yaml] + files: \.ya?ml$ + exclude: ^.*k8s-.*\.ya?ml$|^.github/workflows/.*\.lock\.ya?ml$ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000000..4469efa2f494 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ + + +# Agent Guide for Apache CloudStack + +This file is read by automated agents (security scanners, code +analyzers, AI assistants) operating on this repository. + +## Security + +Security model: [SECURITY.md](./SECURITY.md) + +Agents that scan this repository should consult `SECURITY.md` and the +threat model it links before reporting issues. + +The project-wide security threat model is linked from `SECURITY.md`. diff --git a/CHANGES.md b/CHANGES.md index 9544fc014c75..7a670a55a61f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -242,7 +242,6 @@ Bug ID | Description [CLOUDSTACK-7722](https://issues.apache.org/jira/browse/CLOUDSTACK-7722) | add.label: Add button for tags show the label not "Add" text... [CLOUDSTACK-7246](https://issues.apache.org/jira/browse/CLOUDSTACK-7246) | VM deployment failed due to wrong in script name createipalias.sh... - Version 4.4.1 ------------- @@ -276,7 +275,6 @@ Bug ID | Description [CLOUDSTACK-1632](https://issues.apache.org/jira/browse/CLOUDSTACK-1632) | Mistakes in authorizeSecurityGroup* API docs... [CLOUDSTACK-401](https://issues.apache.org/jira/browse/CLOUDSTACK-401) | Storage options missing from table... - Version 4.4.0 ------------- @@ -646,12 +644,12 @@ Bug ID | Description Version 4.2.1 ------------- -Release notes contain the list of [bug fixes](http://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.2.1/html/Release_Notes/version-4.2.html#issues-fixed-4.2.1) +Release notes contain the list of [bug fixes](https://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.2.1/html/Release_Notes/version-4.2.html#issues-fixed-4.2.1) Version 4.2.0 ------------- Released on October 1 2013. -Release notes contain the list of [bug fixes](http://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.2.0/html/Release_Notes/index.html) +Release notes contain the list of [bug fixes](https://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.2.0/html/Release_Notes/index.html) Version 4.1.0 ------------- @@ -930,7 +928,6 @@ Security Fixes: * CVE-2012-4501: Apache CloudStack configuration vulnerability - Version 4.0.2 ------------------------ @@ -979,7 +976,6 @@ Issues fixed in this release: * CLOUDSTACK-2090: Upgrade from version 4.0.1 to version 4.0.2 triggers the 4.0.0 to 4.0.1. * CLOUDSTACK-2091: Error in API documentation for 4.0.x. - Version 4.0.1-incubating ------------------------ @@ -1023,7 +1019,6 @@ Bugs fixed in this release: * CLOUDSTACK-961: Installation docs don't detail dependencies for building RPMs * CLOUDSTACK-995: Not able to add the KVM host - Version 4.0.0-incubating ------------------------ @@ -1056,7 +1051,6 @@ Security Fixes: * CVE-2012-4501: Apache CloudStack configuration vulnerability - Updating this file ------------------ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index bb84e4e91fb3..f0678ed76498 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,46 +1,79 @@ -Contributing to Apache CloudStack (ACS) -======================================= + + +# Contributing to Apache CloudStack (ACS) + +## Summary + + + + +- [Summary](#summary) +- [Bug fixes](#bug-fixes) +- [Developing new features](#developing-new-features) +- [PendingReleaseNotes file](#pendingreleasenotes-file) +- [Fork the code](#fork-the-code) +- [Making changes](#making-changes) +- [Rebase `feature_x` to include updates from `upstream/main`](#rebase-feature_x-to-include-updates-from-upstreammain) +- [Make a GitHub Pull Request to contribute your changes](#make-a-github-pull-request-to-contribute-your-changes) +- [Cleaning up after a successful pull request](#cleaning-up-after-a-successful-pull-request) +- [Release Principles](#release-principles) + + + +## Summary -Summary -------- This document covers how to contribute to the ACS project. ACS uses GitHub PRs to manage code contributions. -These instructions assume you have a GitHub.com account, so if you don't have one you will have to create one. Your proposed code changes will be published to your own fork of the ACS project and you will submit a Pull Request for your changes to be added. +These instructions assume you have a GitHub.com account, so if you don't have one you will have to create one. Your proposed code changes will be published to your own fork of the ACS project, and you will submit a Pull Request for your changes to be added. -_Lets get started!!!_ +_Let's get started!!!_ -Bug fixes ---------- +## Bug fixes It's very important that we can easily track bug fix commits, so their hashes should remain the same in all branches. Therefore, a pull request (PR) that fixes a bug, should be sent against a release branch. This can be either the "current release" or the "previous release", depending on which ones are maintained. Since the goal is a stable main, bug fixes should be "merged forward" to the next branch in order: "previous release" -> "current release" -> main (in other words: old to new) -Developing new features ------------------------ +## Developing new features Development should be done in a feature branch, branched off of main. Send a PR(steps below) to get it into main (2x LGTM applies). PR will only be merged when main is open, will be held otherwise until main is open again. No back porting / cherry-picking features to existing branches! -PendingReleaseNotes file ------------------------- -When developing a new feature or making a (major) change to a existing feature you are encouraged to append this to the PendingReleaseNotes file so that the Release Manager can +## PendingReleaseNotes file + +When developing a new feature or making a (major) change to an existing feature you are encouraged to append this to the PendingReleaseNotes file so that the Release Manager can use this file as a source of information when compiling the Release Notes for a new release. When adding information to the PendingReleaseNotes file make sure that you write a good and understandable description of the new feature or change which you have developed. Updating the PendingReleaseNotes file is preferably a part of the original Pull Request, but that is up to the developers' discretion. -Fork the code -------------- +## Fork the code In your browser, navigate to: [https://github.com/apache/cloudstack](https://github.com/apache/cloudstack) -Fork the repository by clicking on the 'Fork' button on the top right hand side. The fork will happen and you will be taken to your own fork of the repository. Copy the Git repository URL by clicking on the clipboard next to the URL on the right hand side of the page under '**HTTPS** clone URL'. You will paste this URL when doing the following `git clone` command. +Fork the repository by clicking on the 'Fork' button on the top right hand side. The fork will happen, and you will be taken to your own fork of the repository. Copy the Git repository URL by clicking on the clipboard next to the URL on the right hand side of the page under '**HTTPS** clone URL'. You will paste this URL when doing the following `git clone` command. -On your computer, follow these steps to setup a local repository for working on ACS: +On your computer, follow these steps to set up a local repository for working on ACS: ```bash $ git clone https://github.com/YOUR_ACCOUNT/cloudstack.git @@ -51,10 +84,7 @@ $ git fetch upstream $ git rebase upstream/main ``` - -Making changes --------------- - +## Making changes It is important that you create a new branch to make changes on and that you do not change the `main` branch (other than to rebase in changes from `upstream/main`). In this example I will assume you will be making your changes to a branch called `feature_x`. This `feature_x` branch will be created on your local repository and will be pushed to your forked repository on GitHub. Once this branch is on your fork you will create a Pull Request for the changes to be added to the ACS project. @@ -70,9 +100,7 @@ $ git commit -a -m "descriptive commit message for your changes" > The `-b` specifies that you want to create a new branch called `feature_x`. You only specify `-b` the first time you checkout because you are creating a new branch. Once the `feature_x` branch exists, you can later switch to it with only `git checkout feature_x`. - -Rebase `feature_x` to include updates from `upstream/main` ------------------------------------------------------------- +## Rebase `feature_x` to include updates from `upstream/main` It is important that you maintain an up-to-date `main` branch in your local repository. This is done by rebasing in the code changes from `upstream/main` (the official ACS project repository) into your local repository. You will want to do this before you start working on a feature as well as right before you submit your changes as a pull request. I recommend you do this process periodically while you work to make sure you are working off the most recent project code. @@ -92,13 +120,11 @@ $ git rebase main > Now your `feature_x` branch is up-to-date with all the code in `upstream/main`. +## Make a GitHub Pull Request to contribute your changes -Make a GitHub Pull Request to contribute your changes ------------------------------------------------------ +When you are happy with your changes, and you are ready to contribute them, you will create a Pull Request on GitHub to do so. This is done by pushing your local changes to your forked repository (default remote name is `origin`) and then initiating a pull request on GitHub. -When you are happy with your changes and you are ready to contribute them, you will create a Pull Request on GitHub to do so. This is done by pushing your local changes to your forked repository (default remote name is `origin`) and then initiating a pull request on GitHub. - -Please include JIRA id, detailed information about the bug/feature, what all tests are executed, how the reviewer can test this feature etc. Incase of UI PRs, a screenshot is preferred. +Please include JIRA id, detailed information about the bug/feature, what all tests are executed, how the reviewer can test this feature etc. In case of UI PRs, a screenshot is preferred. > **IMPORTANT:** Make sure you have rebased your `feature_x` branch to include the latest code from `upstream/main` _before_ you do this. @@ -107,7 +133,7 @@ $ git push origin main $ git push origin feature_x ``` -Now that the `feature_x` branch has been pushed to your GitHub repository, you can initiate the pull request. +Now that the `feature_x` branch has been pushed to your GitHub repository, you can initiate the pull request. To initiate the pull request, do the following: @@ -118,9 +144,7 @@ To initiate the pull request, do the following: If you are requested to make modifications to your proposed changes, make the changes locally on your `feature_x` branch, re-push the `feature_x` branch to your fork. The existing pull request should automatically pick up the change and update accordingly. - -Cleaning up after a successful pull request -------------------------------------------- +## Cleaning up after a successful pull request Once the `feature_x` branch has been committed into the `upstream/main` branch, your local `feature_x` branch and the `origin/feature_x` branch are no longer needed. If you want to make additional changes, restart the process with a new branch. @@ -134,6 +158,6 @@ $ git branch -D feature_x $ git push origin :feature_x ``` -Release Principles ------------------- +## Release Principles + Detailed information about ACS release principles is available at https://cwiki.apache.org/confluence/display/CLOUDSTACK/Release+principles+for+Apache+CloudStack+4.6+and+up diff --git a/INSTALL.md b/INSTALL.md index e133e7d7b91b..52f109b0a411 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,15 +1,46 @@ + + # Apache CloudStack Installation basics This document describes how to develop, build, package and install Apache -CloudStack. For more information please refer to the official [documentation](http://docs.cloudstack.apache.org) +CloudStack. For more information please refer to the official [documentation](https://docs.cloudstack.apache.org) or the developer [wiki](https://cwiki.apache.org/confluence/display/CLOUDSTACK/Home). Apache CloudStack developers use various platforms for development, this guide was tested against a CentOS 7 x86_64 setup. -* [Setting up development environment](https://cwiki.apache.org/confluence/display/CLOUDSTACK/Setting+up+CloudStack+Development+Environment) for Apache CloudStack. -* [Building](https://cwiki.apache.org/confluence/display/CLOUDSTACK/How+to+build+CloudStack) Apache CloudStack. -* [Appliance based development](https://github.com/rhtyd/monkeybox) + + + +- [Setting up Development Environment](#setting-up-development-environment) + - [Using jenv and/or pyenv for Version Management](#using-jenv-andor-pyenv-for-version-management) +- [Getting the Source Code](#getting-the-source-code) +- [Building](#building) +- [To bring up CloudStack UI](#to-bring-up-cloudstack-ui) +- [Building with non-redistributable plugins](#building-with-non-redistributable-plugins) +- [Packaging and Installation](#packaging-and-installation) + - [Debian/Ubuntu](#debianubuntu) + - [RHEL/CentOS](#rhelcentos) +- [Notes](#notes) + + ## Setting up Development Environment @@ -18,29 +49,30 @@ Install tools and dependencies used for development: # yum -y install git java-17-openjdk java-17-openjdk-devel \ mysql mysql-server mkisofs git gcc python MySQL-python openssh-clients wget -Set up Maven (3.6.0): +Set up Maven (3.9.10): - # wget http://www.us.apache.org/dist/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz - # tar -zxvf apache-maven-3.6.3-bin.tar.gz -C /usr/local + # wget https://dlcdn.apache.org/maven/maven-3/3.9.10/binaries/apache-maven-3.9.10-bin.tar.gz + # sudo tar -zxvf apache-maven-3.9.10-bin.tar.gz -C /usr/local # cd /usr/local - # ln -s apache-maven-3.6.3 maven + # sudo ln -s apache-maven-3.9.10 maven # echo export M2_HOME=/usr/local/maven >> ~/.bashrc # or .zshrc or .profile # echo export PATH=/usr/local/maven/bin:${PATH} >> ~/.bashrc # or .zshrc or .profile # source ~/.bashrc -Setup up NodeJS (LTS): +Setup up Node.js 16: - # curl -sL https://rpm.nodesource.com/setup_12.x | sudo bash - + # curl -sL https://rpm.nodesource.com/setup_16.x | sudo -E bash - # sudo yum install nodejs # sudo npm install -g @vue/cli npm-check-updates Start the MySQL service: $ service mysqld start + $ mysql_secure_installation ### Using jenv and/or pyenv for Version Management -CloudStack is built using Java and Python. To make selection of these tools versions more consistent and ease installation for developers, optional support for [jenv](http://www.jenv.be/) and [pyenv](https://github.com/yyuu/pyenv) with [virtualenv]|(https://github.com/yyuu/pyenv-virtualenv) is provided. jenv installation instructions are available here and pyenv installation instructions are available here. For users of [oh-my-zsh](http://ohmyz.sh/) there is a pyenv plugin available to trigger configuration of pyenv in a shell session. +CloudStack is built using Java and Python. To make selection of these tools versions more consistent and ease installation for developers, optional support for [jenv](http://www.jenv.be/) and [pyenv](https://github.com/yyuu/pyenv) with [virtualenv]|(https://github.com/yyuu/pyenv-virtualenv) is provided. jenv installation instructions are available here and pyenv installation instructions are available here. For users of [oh-my-zsh](https://ohmyz.sh/) there is a pyenv plugin available to trigger configuration of pyenv in a shell session. Following installation, execute the following commands to configure jenv and pyenv for use with CloudStack development: @@ -86,13 +118,33 @@ Start the management server: If this works, you've successfully setup a single server Apache CloudStack installation. -Open the following URL on your browser to access the Management Server UI: - - http://localhost:8080/client/ +To access the Management Server UI, follow the following procedure: The default credentials are; user: admin, password: password and the domain field should be left blank which is defaulted to the ROOT domain. +## To bring up CloudStack UI + +Move to UI Directory + + $ cd /path/to/cloudstack/ui + +To install dependencies. + + $ npm install + +To build the project. + + $ npm run build + +For Development Mode. + + $ npm start + +Make sure to set `CS_URL=http://localhost:8080` on the `.env.local` file on UI. + +You should be able to run the management server on http://localhost:5050 + ## Building with non-redistributable plugins CloudStack supports several plugins that depend on libraries with distribution restrictions. @@ -150,7 +202,7 @@ All the rpm packages will be created in `dist/rpmbuild/RPMS/x86_64` directory. ## Notes -If you will be using Xen as your hypervisor, please download [vhd-util](http://download.cloudstack.org/tools/vhd-util) +If you will be using Xen as your hypervisor, please download [vhd-util](https://download.cloudstack.org/tools/vhd-util) If management server is installed on RHEL/CentOS, then copy vhd-util into: diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md index 85c1ac9ec6a9..4a48a4e75917 100644 --- a/ISSUE_TEMPLATE.md +++ b/ISSUE_TEMPLATE.md @@ -35,17 +35,14 @@ New line separated list of affected versions, commit ID for issues on main branc Information about the configuration if relevant, e.g. basic network, advanced networking, etc. N/A otherwise --> - ##### OS / ENVIRONMENT - ##### SUMMARY - ##### STEPS TO REPRODUCE + +# pre-commit + +We run [pre-commit](https://pre-commit.com/) with +[GitHub Actions](https://github.com/apache/cloudstack/blob/main/.github/workflows/pre-commit.yml) so installation on your +local machine is currently optional. + +The `pre-commit` [configuration file](https://github.com/apache/cloudstack/blob/main/.pre-commit-config.yaml) +is in the repository root. Before you can run the hooks, you need to have `pre-commit` installed. `pre-commit` is a +[Python package](https://pypi.org/project/pre-commit/). + +From the repository root run: `pip install -r requirements-dev.txt` to install `pre-commit` and after you install +`pre-commit` you will then need to install the pre-commit hooks by running `pre-commit install`. + +The hooks run when running `git commit` and also from the command line with `pre-commit`. Some of the hooks will auto +fix the code after the hooks fail whilst most will print error messages from the linters. If a hook fails the overall +commit will fail, and you will need to fix the issues or problems and `git add` and `git commit` again. On `git commit` +the hooks will run mostly only against modified files so if you want to test all hooks against all files and when you +are adding a new hook you should always run: + +`pre-commit run --all-files` + +Sometimes you might need to skip a hook to commit because the hook is stopping you from committing or your computer +might not have all the installation requirements for all the hooks. The `SKIP` variable is comma separated for two or +more hooks: + +`SKIP=codespell git commit -m "foo"` + +The same applies when running pre-commit: + +`SKIP=codespell pre-commit run --all-files` + +Occasionally you can have more serious problems when using `pre-commit` with `git commit`. You can use `--no-verify` to +commit and stop `pre-commit` from checking the hooks. For example: + +`git commit --no-verify -m "foo"` + +If you are having major problems using `pre-commit` you can always uninstall it. + +To run a single hook use `pre-commit run --all-files ` + +For example just run the `codespell` hook: + +`pre-commit run --all-files codespell` diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index e02cc6518535..be04436d3ff8 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -22,8 +22,8 @@ This PR... - [ ] Bug fix (non-breaking change which fixes an issue) - [ ] Enhancement (improves an existing feature and functionality) - [ ] Cleanup (Code refactoring and cleanup, that may add test cases) -- [ ] build/CI -- [ ] test (unit or integration test code) +- [ ] Build/CI +- [ ] Test (unit or integration test code) ### Feature/Enhancement Scale or Bug Severity @@ -40,10 +40,8 @@ This PR... - [ ] Minor - [ ] Trivial - ### Screenshots (if appropriate): - ### How Has This Been Tested? @@ -53,5 +51,4 @@ This PR... - diff --git a/README.md b/README.md index f66a4dc6f975..852674ab7b1f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,54 @@ -# Apache CloudStack [![Build Status](https://github.com/apache/cloudstack/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/build.yml) [![UI Build](https://github.com/apache/cloudstack/actions/workflows/ui.yml/badge.svg)](https://github.com/apache/cloudstack/actions/workflows/ui.yml) [![License Check](https://github.com/apache/cloudstack/actions/workflows/rat.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/rat.yml) [![Simulator CI](https://github.com/apache/cloudstack/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/ci.yml) [![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=apache_cloudstack&metric=alert_status)](https://sonarcloud.io/dashboard?id=apache_cloudstack) [![codecov](https://codecov.io/gh/apache/cloudstack/branch/main/graph/badge.svg)](https://codecov.io/gh/apache/cloudstack) + + +# Apache CloudStack + +[![Build Status](https://github.com/apache/cloudstack/actions/workflows/build.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/build.yml) +[![codecov](https://codecov.io/gh/apache/cloudstack/branch/main/graph/badge.svg)](https://codecov.io/gh/apache/cloudstack) +[![Docker CloudStack Simulator Status](https://github.com/apache/cloudstack/actions/workflows/docker-cloudstack-simulator.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/docker-cloudstack-simulator.yml) +[![License Check](https://github.com/apache/cloudstack/actions/workflows/rat.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/rat.yml) +[![Linter Status](https://github.com/apache/cloudstack/actions/workflows/linter.yml/badge.svg)](https://github.com/apache/cloudstack/actions/workflows/linter.yml) +[![Merge Conflict Checker Status](https://github.com/apache/cloudstack/actions/workflows/merge-conflict-checker.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/merge-conflict-checker.yml) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=apache_cloudstack&metric=alert_status)](https://sonarcloud.io/dashboard?id=apache_cloudstack) +[![Simulator CI](https://github.com/apache/cloudstack/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/ci.yml) +[![UI Build](https://github.com/apache/cloudstack/actions/workflows/ui.yml/badge.svg?branch=main)](https://github.com/apache/cloudstack/actions/workflows/ui.yml) [![Apache CloudStack](tools/logo/apache_cloudstack.png)](https://cloudstack.apache.org/) + + + +- [Who Uses CloudStack?](#who-uses-cloudstack) +- [Demo](#demo) +- [Getting Started](#getting-started) +- [Getting Source Repository](#getting-source-repository) +- [Documentation](#documentation) +- [News and Events](#news-and-events) +- [Getting Involved and Contributing](#getting-involved-and-contributing) +- [Reporting Security Vulnerabilities](#reporting-security-vulnerabilities) +- [License](#license) +- [Notice of Cryptographic Software](#notice-of-cryptographic-software) +- [Star History](#star-history) +- [Contributors](#contributors) + + + Apache CloudStack is open source software designed to deploy and manage large networks of virtual machines, as a highly available, highly scalable Infrastructure as a Service (IaaS) cloud computing platform. CloudStack is used @@ -21,7 +68,7 @@ OVM and LXC containers. Users can manage their cloud with an easy to use Web interface, command line tools, and/or a full-featured query based API. -For more information on Apache CloudStack, please visit the [website](http://cloudstack.apache.org) +For more information on Apache CloudStack, please visit the [website](https://cloudstack.apache.org) ## Who Uses CloudStack? @@ -78,10 +125,10 @@ via GitHub pull requests. ## Getting Involved and Contributing Interested in helping out with Apache CloudStack? Great! We welcome -participation from anybody willing to work [The Apache Way](http://theapacheway.com) and make a +participation from anybody willing to work [The Apache Way](https://theapacheway.com) and make a contribution. Note that you do not have to be a developer in order to contribute to Apache CloudStack. We need folks to help with documentation, translation, -promotion etc. See our contribution [page](http://cloudstack.apache.org/contribute.html). +promotion etc. See our contribution [page](https://cloudstack.apache.org/contribute.html). If you are a frequent contributors, you can request to be added as collaborators (see https://cwiki.apache.org/confluence/display/INFRA/Git+-+.asf.yaml+features#Git.asf.yamlfeatures-AssigningexternalcollaboratorswiththetriageroleonGitHub) @@ -92,7 +139,7 @@ You may do so by sharing your GitHub users ID or raise a GitHub issue. If you're interested in learning more or participating in the Apache CloudStack project, the mailing lists are the best way to do that. While the project has -several communications channels, the [mailing lists](http://cloudstack.apache.org/mailing-lists.html) are the most active and the +several communications channels, the [mailing lists](https://cloudstack.apache.org/mailing-lists.html) are the most active and the official channels for making decisions about the project itself. Mailing lists: @@ -112,7 +159,7 @@ released version of CloudStack, please report it to `security@apache.org` with details about the vulnerability, how it might be exploited, and any additional information that might be useful. -For more details, please visit our security [page](http://cloudstack.apache.org/security.html). +For more details, please visit our security [page](https://cloudstack.apache.org/security.html). ## License @@ -156,7 +203,15 @@ 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. + +## Star History + +[![Apache CloudStack Star History](https://api.star-history.com/svg?repos=apache/cloudstack&type=Date)](https://www.star-history.com/#apache/cloudstack&Date) + +## Contributors + +[![Apache CloudStack Contributors](https://contrib.rocks/image?repo=apache/cloudstack&anon=0&max=500)](https://github.com/apache/cloudstack/graphs/contributors) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 000000000000..018f2fa8cb86 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,34 @@ + + +# Security Policy + +## Reporting a Vulnerability + +`apache/cloudstack` follows the [Apache Software Foundation security process](https://www.apache.org/security/). Please report suspected +vulnerabilities privately to `security@apache.org`; do not open public GitHub issues or pull requests for security reports. + +For more details, see https://cloudstack.apache.org/security.html. + +## Threat Model + +What the project treats as in scope and out of scope, the security +properties it provides and disclaims, the adversary model, and how +findings are triaged are documented in the project-wide threat model: +[THREAT_MODEL.md](THREAT_MODEL.md). diff --git a/THREAT_MODEL.md b/THREAT_MODEL.md new file mode 100644 index 000000000000..4659b937dedb --- /dev/null +++ b/THREAT_MODEL.md @@ -0,0 +1,1148 @@ + + +# Apache CloudStack Security Threat Model (draft) + +> **Document scope and PMC structural decision.** The CloudStack PMC owns +> five repositories: `apache/cloudstack` (the management server, agent, and +> systemvm), plus four satellite clients — `apache/cloudstack-cloudmonkey` +> (CLI), `apache/cloudstack-go` (Go SDK), `apache/cloudstack-terraform-provider`, +> `apache/cloudstack-kubernetes-provider`. This document models +> `apache/cloudstack` as the canonical threat model; the four satellite +> models are short *deltas* that inherit §3 / §4 / §7 from this +> document and add only what each satellite uniquely introduces (`§4 B1` +> reachability, the credential file shape, the wrapper-of-SDK contract, +> etc.). The deltas live at `/tmp/claude/cloudstack--threat-model-draft.md`. +> The satellite clients' interfaces point **inward** at the management-server +> API; some satellites additionally expose **outward** interfaces that are +> designed to be safe to expose *(maintainer: DaanHoogland)*. +> An umbrella model was rejected because the satellites are uniformly thin +> "HMAC-SHA1-signing HTTP client" wrappers — a single document either +> drowns them in CloudStack-server content or, worse, drowns the +> CloudStack-server content in satellite caveats. Each satellite is small +> enough that a 1–2 page delta works. + +## §1 Header + +- **Project:** Apache CloudStack (`apache/cloudstack`) — IaaS orchestration + platform. This document does **not** cover the four satellite repos, which + carry their own delta models. +- **Commit:** `7308dad1` (HEAD of `main` at draft time). +- **Date:** 2026-05-29. +- **Authors:** ASF Security team draft, awaiting CloudStack PMC review. +- **Status:** Draft — under maintainer review. +- **Version binding:** This document describes the model as of the commit + above. A vulnerability report against CloudStack release *N* (currently + the 4.20.x line) should be triaged against the model as it stood at *N*'s + release tag, not against HEAD. +- **Reporting:** vulnerabilities that fall under §8 (claimed properties) + should be reported per the project's published policy + (`security@apache.org` per `README.md` and + `https://cloudstack.apache.org/security.html`); reports that fall under + §3 (out of scope), §9 (properties not provided), or §11a (known + non-findings) will be closed by CloudStack triagers citing this document. +- **Provenance legend** — + *(documented)* = paraphrased from an in-repo source or the project website + with citation; *(maintainer)* = stated by a CloudStack PMC member in + response to this draft; *(inferred)* = synthesized by the producer from + code structure or domain knowledge, awaiting PMC ratification (every + *(inferred)* tag has a matching §14 question). +- **Draft confidence (provenance-tag tally):** 51 *(documented)* / 42 + *(maintainer)* / 38 *(inferred)*. Eleven formerly-open questions (Q1, + Q2, Q4, Q5, Q12 — including the highest-leverage Root-CA strictness + default — plus Q8, Q9, Q10, Q17, Q18, Q19 from the 2026-06-08 review) + were resolved by the CloudStack PMC review (DaanHoogland, vishesh92) and + their tags promoted from *(inferred)* to *(maintainer)*. + +**About the project.** Apache CloudStack is an open-source Infrastructure-as-a- +Service (IaaS) orchestration platform *(documented: `README.md`, +`https://cloudstack.apache.org/`)*. It deploys and manages large fleets of +virtual machines across multiple hypervisors (KVM, VMware, XenServer/XCP-ng, +Hyper-V, baremetal-bridge, OVM) and over object/block/file storage +(NFS, Ceph/RBD, iSCSI, SMB, primary-storage plugins, S3-compatible secondary +storage). A central **management server** (Java/Tomcat-style servlets, +backed by MariaDB/MySQL) exposes a signed REST/JSON API to admins, end +users, and integrations; runs system VMs (Secondary Storage VM, Console +Proxy VM, virtual router); and orchestrates a fleet of **agents** running +on each hypervisor host. Authorization is RBAC + multi-tenant +domain/account/project hierarchy. The deployment shape is "operator-run +private/public cloud control plane", not a hosted-as-a-service appliance. + +## §2 Scope and intended use + +### Intended use + +- A multi-tenant IaaS control plane deployed by an operator inside a + controlled datacenter or cloud, exposing compute / storage / network + orchestration to authenticated end users via APIs which return responses in JSON or XML and a Vue.js Web + UI, with separately authenticated administrators *(documented: `README.md`, + `INSTALL.md`)*. +- Used both for service-provider public clouds and for on-premises private + clouds; the trust model is the same in both *(documented: `README.md`)*. + +### Deployment shape + +CloudStack is **not** an in-process library, **not** a single-binary +appliance, and **not** a hosted SaaS. It is a distributed control plane: +one or more management-server instances — **a single management-server +instance for smaller clouds, or a cluster behind a load balancer for +larger deployments** *(maintainer: DaanHoogland)* — a MariaDB/MySQL +database, one usage server, an optional +SecondaryStorageVM/ConsoleProxyVM/VirtualRouter set of system VMs, and a +per-hypervisor-host `cloudstack-agent` (for KVM/baremetal) or +out-of-process resource bridges (for VMware / XenServer / XCP-ng / Hyper-V). +The operator owns the surrounding L2/L3 network (the **management network**, +the **public network**, the **guest network**, the **storage network**) +and the physical hosts. The threat model is therefore that of a +distributed service (single-instance or clustered), not a library +*(maintainer: DaanHoogland — confirms the distributed control-plane shape; +single-instance is also a supported topology)*. + +### Caller roles + +| Role | Trust level | Notes | +| --- | --- | --- | +| **End-user API client / Web UI user** | untrusted but authenticated | Identity verified via Apache CloudStack-native (password + HMAC-SHA1 signed request), LDAP, SAML2, OAuth2, or pluggable `APIAuthenticator` *(documented: `plugins/user-authenticators/{ldap,saml2,oauth2,...}`, `server/src/main/java/com/cloud/api/ApiServer.java` `verifyRequest`)*. | +| **Domain / Project admin** | partial trust within their domain | Bounded by RBAC (`plugins/acl/{static,dynamic,project}-role-based`) and the domain hierarchy; can manage users / VMs / networks within a domain. | +| **Root admin** | trusted control plane | Global RBAC role; can change global configuration, upload templates/ISOs, run privileged orchestration. | +| **Operator / cluster admin** | trusted | OS-level access to management-server hosts, the MariaDB database, the keystore, and the agent hosts. Sets `agent.properties`, manages `cloudstack-agent` packages, manages the JCEKS keystore used by the agent for TLS *(documented: `agent/conf/agent.properties`, `framework/security/.../KeystoreManager.java`)*. | +| **Hypervisor agent (cloudstack-agent on KVM/baremetal)** | trusted-once-enrolled peer | Mutually authenticated via X.509 client cert signed by the management server's Root CA *(documented: `framework/ca/`, `plugins/ca/root-ca/`, `agent/src/main/java/com/cloud/agent/Agent.java` `setupAgentKeystore`)*. | +| **System VM (SSVM / CPVM / VR)** | trusted-once-enrolled peer | Same X.509 enrolment shape as the agent; carries the agent binary inside *(maintainer: confirmed — same trust tier as agents, not a separate tier)*. | +| **Hypervisor host (the underlying KVM/VMware/etc.)** | trusted by virtue of operator-controlled provisioning | CloudStack expects to drive the hypervisor via libvirt / VMware vSphere SDK / XenAPI as a privileged user *(documented: `plugins/hypervisors/kvm/`, `plugins/hypervisors/vmware/`, `plugins/hypervisors/xenserver/`)*. | +| **Hypervisor-managed guest VM (end-user workload)** | **untrusted** | A guest VM is an attacker's workload; the model defends against it. | +| **Reverse proxy / load balancer in front of management server** | trusted *(if `proxy.header.verify=true`)* | When the operator enables forward-header processing, only requests whose `Remote_Addr` ∈ `proxy.cidr` have their `proxy.header.names` header honoured *(documented: `server/src/main/java/com/cloud/api/ApiServlet.java` `getClientAddress`; setting names maintainer: vishesh92)*. | +| **Underlying storage (primary / secondary)** | trusted by virtue of operator-granted credentials | CloudStack reads/writes via NFS / RBD / iSCSI / S3 with operator-supplied credentials *(documented: primary/secondary storage plugins under `plugins/storage/`)*. | +| **External integrations (Tungsten, NSX, Netscaler, Palo Alto, …)** | trusted control-plane peers | Operator-configured; CloudStack assumes truthful responses *(inferred — §14 Q3)*. | + +### Component-family table + +| Management server JSON and XML APIs | `server/src/main/java/com/cloud/api/ApiServlet.java`, HTTP on `:8080` (API + UI), optional HTTPS on `:8443` when `https.enable=true`; user API path `:8080/client/api` *(documented: `server/src/main/java/com/cloud/api/ApiServlet.java`, `client/conf/server.properties.in`)* | network (TCP, optionally TLS) | **yes** | +| Management server cluster RPC (peer-to-peer) | NIO + TLS between management-server replicas, `:9090` *(documented: `framework/cluster/`, `utils/.../nio/`)* | network | **yes** (peer auth via Root CA) | +| Management server → agent RPC | NIO + TLS on `:8250` (default `agent.properties`) *(documented: `agent/conf/agent.properties` line 47, `utils/.../nio/NioServer.java`)* | network | **yes** (mutually authenticated via Root CA) | +| `cloudstack-agent` (KVM/baremetal) | reverse-connects to management server, runs commands via libvirt / hypervisor SDK *(documented: `agent/`, `plugins/hypervisors/kvm/`)* | network + hypervisor + OS | **yes** | +| System VMs — SecondaryStorageVM, ConsoleProxyVM, Virtual Router | shipped images under `systemvm/`; agent binaries inside them *(documented: `systemvm/`)* | network (storage / public / guest) | **yes** | +| Console proxy data path | browser ↔ ConsoleProxyVM ↔ hypervisor VNC/SPICE socket; signed token issued by management server *(documented: `server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java`, `server/src/main/java/com/cloud/servlet/ConsoleProxyPasswordBasedEncryptor.java`)* | network | **yes** | +| Secondary-storage HTTP (templates, ISO downloads, snapshot copies) | download links are UUID-named symlinks served by an Apache httpd, with **no auth on the link**; the UUID format prevents enumeration and the symlinks are removed after a period *(maintainer: vishesh92, DaanHoogland)* | network | **yes** | +| Hypervisor plugins (`plugins/hypervisors/{kvm,vmware,xenserver,hyperv,ovm,ovm3,baremetal,ucs,simulator}`) | invoked by agent or by management server *(documented: `plugins/hypervisors/`)* | hypervisor APIs | **yes** for the call shape; **out-of-model** for the upstream hypervisor's own bugs | +| Network plugins (`plugins/network-elements/{netscaler,nsx,palo-alto,tungsten,nicira-nvp,...}`) | management server outbound | external SDN/firewall APIs | **yes** for credential handling and request construction; **out-of-model** for the external endpoint | +| Storage plugins (`plugins/storage/{volume,image,object}`) | management server / agent | NFS, RBD, iSCSI, S3 endpoints | **yes** for credential handling; **out-of-model** for the storage endpoint | +| User authenticator plugins (`plugins/user-authenticators/{md5,sha256salted,pbkdf2,plain-text,ldap,saml2,oauth2}`) | management server | LDAP / SAML2 IdP / OAuth2 IdP | **yes** for the local code; **out-of-model** for the IdP | +| RootCA provider (`plugins/ca/root-ca/`) | self-signed CA generated by management server at first boot, issues certs to agents *(documented: `plugins/ca/root-ca/.../RootCAProvider.java`)* | none directly | **yes** | +| Two-factor authenticators (`plugins/user-two-factor-authenticators/{static-pin,totp}`) | management server | none | **yes** | +| Backup providers (`plugins/backup/`) | management server outbound | external backup endpoints | **yes** for credential handling | +| Quota / metrics / DRS / HA planners | internal | none | **yes** as orchestration only; not a security boundary | +| Database layer (MariaDB/MySQL, Jasypt-encrypted secrets) | management server | network to DB | **yes** for credential handling; DB itself is trusted *(documented: `README.md` "Notice of Cryptographic Software" — JaSypt, native DB encryption)* | +| `cloud-cli`, `tools/marvin`, `test/`, `developer/`, `quickcloud/` | integration / test tooling | varies | **out of model** *(§3)* | +| `systemvm/agent/noVNC` (a vendored fork of `github.com/novnc/novnc` with CloudStack-specific changes on top *(maintainer: vishesh92)*), `…/vendor/pako`, other vendored JS / shell scripts | vendored upstream | n/a | in-model only at the wrapper boundary; upstream bugs go upstream. No automated vendored-dependency update procedure exists today (dependabot does not produce viable PRs); the PMC would prefer to have one *(maintainer: DaanHoogland)* | + +## §3 Out of scope (explicit non-goals) + +CloudStack is not, and does not aim to be, the following — reports +requiring any of these will be closed with the cited disposition: + +1. **A defender against the operator.** Anyone with `root` on a + management-server host, `root` on a hypervisor host, raw MariaDB + credentials, the JCEKS keystore + `security.encryption.key` / + `security.encryption.iv` *(documented: + `framework/security/.../KeysManager.java`)*, or the Root CA private key + already has unbounded power. "The operator misconfigured X" is not a + vulnerability *(inferred — §14 Q6)*. → `OUT-OF-MODEL: + adversary-not-in-scope`. +2. **A defender against a malicious external service the operator + configured.** A hostile LDAP server, SAML IdP, OAuth IdP, Tungsten / + NSX / Netscaler controller, S3 endpoint, Ceph cluster, or backup + provider is treated as a trusted control-plane peer. If the report + requires that peer to be hostile, it is out of model *(inferred — + §14 Q3)*. → `OUT-OF-MODEL: trusted-input`. +3. **A defender against the hypervisor.** CloudStack drives KVM / VMware / + XenServer / XCP-ng / Hyper-V via their own admin APIs. A hypervisor + bug that allows guest escape, a vSphere SDK vulnerability, a libvirt + privilege escalation — all are upstream to the hypervisor project, not + to CloudStack *(inferred — §14 Q7)*. → `OUT-OF-MODEL: + unsupported-component` (upstream pointer). +4. **An isolation boundary between an authorized administrator's API + call and the management server process.** Root admin can change global + configuration, upload templates and scripts to system VMs, register + arbitrary network/storage plugins, and run `runCustomAction`-style + commands. A new way for a root admin to do something they are already + authorized to do is not a vulnerability *(maintainer: vishesh92 — §14 Q8)*. → + `OUT-OF-MODEL: equivalent-harm`. +5. **A defender against a guest VM doing things the hypervisor allows it + to do.** A guest VM consuming CPU, memory, or disk up to its allocated + limit, sending arbitrary IP traffic within its assigned VLAN / VXLAN / + security group, or exploiting another VM via the hypervisor's own + shared surfaces (sidechannel, RowHammer, GPU passthrough leak) is out + of model. CloudStack is responsible only for the orchestration that + *places* the guest, not for hypervisor-level isolation *(maintainer: + vishesh92 — §14 Q9; the in-model case is CloudStack applying + wrong/insecure hypervisor settings — Daan to confirm boundary)*. → `OUT-OF-MODEL: adversary-not-in-scope` for the + side-channel case, `BY-DESIGN: property-disclaimed` for the + resource-limit case. +6. **A sandbox for templates, ISO images, or user-data scripts.** A + user-uploaded template (via `registerTemplate`) is run by the + hypervisor with the privileges the system grants. cloud-init / + user-data / metadata is passed through to the guest; CloudStack does + not parse or sanitize its semantics *(documented: kubernetes-service + plugin `userdata` references; maintainer: vishesh92 — §14 Q10, end-user guest customization)*. → + `BY-DESIGN: property-disclaimed`. +7. **Code that ships but is not part of the supported product:** + `tools/marvin/`, `test/`, `developer/`, `quickcloud/`, `cloud-cli/`, + `tools/devcloud4/`, `tools/devcloud-kvm/`, `tools/appliance/`, + `tools/checkstyle/`, `tools/transifex/`, `services/`-side simulators, + `simulator` hypervisor plugin, and IDE / build helpers under `tools/`. + *(inferred — §14 Q11)*. → `OUT-OF-MODEL: unsupported-component`. +8. **Bundled / vendored upstream libraries** — JaSypt, Bouncy Castle, + JSch, OpenSwan, noVNC + `pako`, MariaDB Connector/J, Spring, + Apache Commons, log4j, etc. *(documented: `README.md` Cryptographic + Software notice)*. `systemvm/agent/noVNC` is specifically a **vendored + fork of `github.com/novnc/novnc`** carrying CloudStack-specific changes + *(maintainer: vishesh92)*. Where CloudStack vendors source, the vendored + code is modeled at the wrapper boundary; vulnerabilities intrinsic to the + upstream project should be reported upstream. There is currently **no + automated procedure** to pull upstream fixes into the vendored copies + (dependabot has not produced viable PRs); the PMC would prefer to + establish one *(maintainer: DaanHoogland)*. + → `OUT-OF-MODEL: unsupported-component` (with an upstream pointer). +9. **The four satellite repos** (`apache/cloudstack-cloudmonkey`, + `apache/cloudstack-go`, `apache/cloudstack-terraform-provider`, + `apache/cloudstack-kubernetes-provider`) — covered by their own delta + threat models which inherit §3 / §4 / §7 from this document. +10. **The CloudStack documentation site, Confluence wiki, downloads + mirrors, Docker Hub images outside `apache/cloudstack-*`, gem / + npm / PyPI packages with similar names, and other non-product + surfaces.** Out of scope. + +## §4 Trust boundaries and data flow + +CloudStack has at least nine distinct trust transitions; a finding is +in-model only when it cleanly maps to one of them. + +| # | Transition | Authentication | Authorization | +| --- | --- | --- | --- | +| B1 | API client → management server JSON API (`:8080`/`:8443`) | per-user API key + HMAC-SHA1 signature over query string, or session login + 2FA *(documented: `server/src/main/java/com/cloud/api/ApiServer.java` `verifyRequest`)*; signature version 3 has expiration enforcement *(documented: same file line ~1053)* | RBAC (dynamic-role-based / static-role-based / project-role-based) on the called API command name + domain/account ownership of named resources | +| B2 | Web UI → management server (`:8080`) | same as B1 plus session cookie | same as B1 | +| B3 | Browser → ConsoleProxyVM → hypervisor VNC socket | signed token issued by management server, embedded in URL; encrypted with `ConsoleProxyPasswordBasedEncryptor` *(documented: `server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java`, `ConsoleProxyPasswordBasedEncryptor.java`)* | implicit (signed-token possession) | +| B4 | Management server ↔ management server (cluster peers) | NIO + TLS, Root CA-issued certs *(documented: `framework/cluster/`, `framework/ca/`)* | peer-trust by valid cert | +| B5 | Management server → `cloudstack-agent` (KVM/baremetal) | NIO + TLS on `:8250`; agent uses X.509 client cert issued by Root CA on first connect; cert provisioning is the `SetupKeyStoreCommand` shape *(documented: `agent/src/main/java/com/cloud/agent/Agent.java` `setupAgentKeystore`, `framework/ca/.../CAService.java`, `plugins/ca/root-ca/.../RootCAProvider.java`)*; trust strictness governed by `ca.plugin.root.auth.strictness` (**default `true` for new setups; `false` only on upgrade from pre-Aug-2017 versions** — see §5a) and `ca.plugin.root.allow.expired.cert` | peer-trust by valid cert | +| B6 | Management server → external services (LDAP / SAML2 / OAuth2 IdP, NSX, Netscaler, Tungsten, S3, backup providers) | per-provider (service account, OAuth token, etc.) | external-service-side | +| B7 | Agent → hypervisor (libvirt / vSphere SDK / XenAPI) | local Unix socket (libvirt) or operator-supplied SDK credentials | hypervisor-side | +| B8 | Management server / agent → primary/secondary storage (NFS, RBD, iSCSI, S3) | OS-level (NFS), Ceph cephx, iSCSI CHAP, IAM key / static credential (S3) | storage-side | +| B9 | Operator → management server config (`db.properties`, `server.properties`, JCEKS keystore, global config table) | filesystem permissions on the host + DB access | OS-level + DB-level | + +### Reachability preconditions per family + +For each family in §2, a finding is in-model only if it is reachable as +follows: + +- **Management server JSON API**: reachable from an *unauthenticated* network + peer who can reach `:8080` / `:8443`. Findings that require an + authenticated peer collapse to "authenticated user with RBAC privilege + X", and must additionally either clear RBAC for the harmful command or + bypass it. +- **Web UI**: same shape as the JSON API; the Vue.js SPA is a presentation + layer over the API. +- **Cluster RPC (B4)**: reachable from a peer that has cleared the Root CA + trust check. A flat "cluster RPC has no auth" finding is `OUT-OF-MODEL: + adversary-not-in-scope` because the model *requires* the Root CA to be + enrolled across peers; a *cleartext*/un-certed cluster RPC finding is + gated by `ca.plugin.root.auth.strictness`, which defaults to `true` on + new setups (see §5a). +- **Management ↔ agent (B5)**: reachable from a peer that presents a + Root-CA-signed certificate the management server accepts. By default on + new setups `ca.plugin.root.auth.strictness = true`, so the management + server **does require** a client certificate from the connecting agent + *(maintainer: vishesh92 — + `https://github.com/apache/cloudstack/pull/2239`)*. The value remains + `false` only when upgrading from versions released before Aug 2017 that + predate the setting; that upgrade case is documented in the upgrade + instructions and is therefore not a concern *(maintainer: DaanHoogland)* + *(documented: `plugins/ca/root-ca/.../RootCAProvider.java`, + `RootCACustomTrustManager.java`)*. +- **Console proxy (B3)**: reachable by anyone who holds a valid signed + token. The token is the entire authorization gate. +- **Agent → hypervisor (B7)**: reachable only on the agent host, by code + the agent runs. +- **External integrations (B6)**: reachable from the management server's + outbound posture; a hostile external service is `OUT-OF-MODEL: + trusted-input` (§3 item 2). + +## §5 Assumptions about the environment + +- **Operating system (management server / usage server)**: RHEL 8/9/10, + CentOS 8/9, Rocky 9/10, Ubuntu 22.04/24.04, SUSE 15, openSUSE Leap 15; + Java 17 (`README.md`, `INSTALL.md`, `packaging/{el8,el9,el10,debian,suse15}`). +- **Operating system (agent)**: same family on KVM/baremetal hosts; + agent ships as `cloudstack-agent` package *(documented: `debian/`, + `packaging/`)*. +- **Database**: MariaDB or MySQL-compatible, accessible from each + management-server instance; CloudStack uses native DB encryption + + JaSypt for application-level secrets *(documented: `README.md` + "Notice of Cryptographic Software")*. +- **Cryptography**: JaSypt (application-secret encryption), Bouncy Castle + (general-purpose crypto, X.509 issuance in the Root CA provider), JSch + (SSH client to system VMs), OpenSwan (optional VPN endpoint termination) + *(documented: `README.md` Cryptographic Software notice)*. +- **Network**: operator-controlled L2/L3 with at least the management + network, public network, guest network, and storage network as logical + fabrics *(documented: CloudStack admin documentation; inferred — + §14 Q13)*. The management network is the trusted control-plane + network; the guest network carries untrusted guest VM traffic. +- **Time**: signature version 3 enforces an `expires` parameter on signed + API requests *(documented: `ApiServer.java` line ~1054)*; this assumes + loosely-synchronized clocks between client and management server + *(inferred — §14 Q14)*. +- **Filesystem**: the JCEKS keystore, `db.properties`, `server.properties`, + and Root CA private key are stored under `/etc/cloudstack/management/` + with OS-level permissions restricted to the `cloudstack` user + *(inferred — §14 Q15)*. +- **Hypervisor**: each supported hypervisor is assumed to provide its own + guest isolation (memory, vCPU, disk, network) and to expose a stable + admin API (libvirt for KVM, vSphere SDK for VMware, XenAPI for + XenServer/XCP-ng, WinRM/Hyper-V API for Hyper-V). +- **What CloudStack does to its host** (negative claims, awaiting + maintainer ratification): + - **does** open listening sockets on documented ports + (`:8080`/`:8443`/`:8250`/`:8096`/`:9090`/console-proxy ports) *(documented)*; + - **does** maintain MariaDB connections from the management server; + - **does** issue X.509 certificates from its self-signed Root CA *(documented: + `plugins/ca/root-ca/.../RootCAProvider.java`)*; + - **does** spawn child processes from the agent (`Script` invocations + against `/usr/share/cloudstack-common/scripts/`) *(documented: + `agent/src/main/java/com/cloud/agent/Agent.java` `keystoreSetupSetupPath`, + `keystoreCertImportScriptPath`)*; + - **does** write logs under operator-configured locations; + - **does** read a documented set of environment variables and the + `db.properties` file at startup *(inferred — §14 Q16)*; + - **does** install signal handlers / shutdown hooks only as + Tomcat/Jetty servlet container default *(inferred — §14 Q16)*. + +## §5a Build-time and configuration variants + +CloudStack ships as a family of packages: 'cloudstack-agent', 'cloudstack-baremetal-agent', 'cloudstack-common', 'cloudstack-integration-tests', 'cloudstack-management', 'cloudstack-marvin', 'cloudstack-mysql-ha', 'cloudstack-ui', 'cloudstack-usage' +*(documented: `debian/`, `packaging/`)*. A sizable number of runtime +configuration knobs materially change the security envelope. The +security-relevant subset: + +| Knob | Default | Maintainer stance | Effect | +| --- | --- | --- | --- | +| `ca.plugin.root.auth.strictness` | **`true` for new setups; `false` only on upgrade from pre-Aug-2017 versions** *(maintainer: vishesh92 — `https://github.com/apache/cloudstack/pull/2239`)* | New setups are strict by default; the `false`-on-upgrade case is called out in the upgrade instructions and is therefore not a concern *(maintainer: DaanHoogland)* | When `false`, the management server's `RootCACustomTrustManager` does **not** require a client certificate from a peer attempting to connect on `:8250` (agent port) or cluster ports. A peer without a cert is allowed in. | +| `ca.plugin.root.allow.expired.cert` | **`true`** *(documented: `RootCAProvider.java`)* | operational default to survive cert-rotation lag *(maintainer: paired with the strictness ruling above)* | When `true`, an expired client cert is accepted during SSL handshake. | +| `ca.plugin.root.issuer.dn` | `CN=ca.cloudstack.apache.org` *(documented: same file)* | configured at first management-server boot | Subject DN of the auto-generated self-signed Root CA. | +| `proxy.header.verify` | `false` by default *(maintainer: vishesh92 — §14 Q17)* | When on, the operator must restrict `proxy.cidr` to the trusted reverse-proxy CIDR | When set, `ApiServlet.getClientAddress` honours proxy-set forward headers *only* for source IPs in `proxy.cidr` *(documented: `server/src/main/java/com/cloud/api/ApiServlet.java` `getClientAddress`; setting name maintainer: vishesh92)*. | +| `proxy.header.names` | list of header names; semantics: names to check for allowed IP addresses from a proxy-set header *(maintainer: vishesh92)* | list of header names to consult for the allowed client address when set by a proxy | Names the request header(s) carrying the proxy-set client IP. | +| `proxy.cidr` | unset *(maintainer: vishesh92 — §14 Q17; headers honoured only when `Remote_Addr` ∈ this list)* | required when `proxy.header.verify` is on | List of CIDRs for which `proxy.header.names` headers are honoured when the connecting `Remote_Addr` is in this list *(semantics maintainer: vishesh92)*. | +| `enable.user.2fa` / `mandate.user.2fa` | both default `false`; domain-configurable *(maintainer: vishesh92 — §14 Q18)* | `enable.user.2fa` turns 2FA on; `mandate.user.2fa` makes it mandatory (only when `enable.user.2fa` is true) — a deployment choice, not a §10 violation when off | When on, users must complete static-pin or TOTP 2FA after login. | +| `security.encryption.key`, `security.encryption.iv` | auto-generated at first boot *(documented: `framework/security/.../KeysManager.java`)* | trusted secret | Base64-encoded JaSypt master key + IV used to encrypt application-level secrets in the DB. | +| `user.password.encoders.order` | `PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT` *(maintainer: vishesh92)* | first encoder in the order is used to hash new passwords; the list also defines the verification fall-through order | Governs how user passwords are stored and which encoders are accepted on verify. | +| `user.password.encoders.exclude` | `MD5,LDAP,PLAINTEXT` *(maintainer: vishesh92)* | excluded encoders are not used to (re)hash passwords | Excludes weak/legacy encoders from being chosen, even though they remain in the order list for verifying already-stored hashes. | +| `enforce.post.requests.and.timestamps` | per `isPostRequestsAndTimestampsEnforced` *(documented: `ApiServer.java`; setting name maintainer: vishesh92)* | bounds `expires` to a maximum future offset | Prevents an attacker who steals a signed URL with a 10-year expiration from using it forever. | +| `integration.api.port` (`:8096`) | typically disabled *(inferred — §14 Q20)* | When non-zero, exposes an *unauthenticated* admin API for integration testing | An open integration port is a complete RBAC bypass on the management server. | +| Hypervisor enablement (which `plugins/hypervisors/*` are installed and configured) | per zone | operator-driven | An unused hypervisor plugin still ships but is not connected to any host. | +| Hostname / SAN of management-server cert (`ca.framework.cert.management.custom.san`) | unset *(maintainer: vishesh92)* | when set, included in the auto-generated cert SAN | governs which hostnames clients can use to reach the management server. | +| SAML2 / OAuth2 enablement (`plugins/user-authenticators/{saml2,oauth2}`) | off *(inferred — §14 Q19)* | turning on adds an external IdP trust dependency | adds B6 transitions. | +| LDAP enablement (`plugins/user-authenticators/ldap`) | off *(inferred — §14 Q19)* | turning on adds an external LDAP trust dependency | adds B6 transitions. | + +**The Root-CA strictness default (resolved).** Earlier drafts treated +`ca.plugin.root.auth.strictness = false` as the shipped default and the +single highest-leverage open question. The PMC has clarified that **new +setups default to `true`** — the management server *does* require a +Root-CA-signed client cert on `:8250` and the cluster ports — and the +value is `false` **only** when upgrading from versions released before +Aug 2017 that predate the setting *(maintainer: vishesh92 — +`https://github.com/apache/cloudstack/pull/2239`)*. That upgrade case is +documented in the upgrade instructions, so a leftover `false` after such +an upgrade is an operator-hardening/upgrade-hygiene item, not a shipped +insecure default *(maintainer: DaanHoogland)*. A report against an open +`:8250` accepting an un-certed peer on a **new** install is therefore +`MODEL-GAP`/`VALID` (strictness should be on), whereas the same on an +**upgraded** pre-2017 install is `OUT-OF-MODEL: non-default-build` +(documented upgrade step not applied). `ca.plugin.root.allow.expired.cert` +remains `true` as an operational concession to cert-rotation lag. + +## §6 Assumptions about inputs + +### Per-endpoint trust table (network surfaces) + +| Surface / route | Parameter | Attacker-controllable? | Caller must enforce | +| --- | --- | --- | --- | +| Management server `:8080`/`:8443` JSON API | command name + params | **yes** | nothing — CloudStack parses, authenticates (B1), applies RBAC, dispatches | +| Management server `:8080`/`:8443` JSON API | `signature` parameter | **yes** | HMAC-SHA1 verified *constant-time* against expected signature *(documented: `ApiServer.java` line 1137 `ConstantTimeComparator.compareStrings`)* | +| Management server `:8080`/`:8443` JSON API | `expires` parameter (sig v3) | **yes** | rejected if past, or beyond the `enforce.post.requests.and.timestamps` ceiling *(documented: same file; setting name maintainer: vishesh92)* | +| Management server `:8080`/`:8443` JSON API | proxy-set forward headers (`proxy.header.names`) | **yes** if `proxy.header.verify=true` | honoured **only** if the connecting `Remote_Addr` ∈ `proxy.cidr` *(documented: `ApiServlet.java` `getClientAddress`; setting names maintainer: vishesh92)* | +| Management server `:8080`/`:8443` Web UI | session cookie | **yes** | session-fixation / invalidation handled via `invalidateHttpSession` on auth failure *(documented: `ApiServlet.java` line 418)* | +| Integration API `:8096` (if enabled) | command name + params | **yes** | **no signature check** — integration port is unauthenticated by design | +| Management ↔ agent `:8250` | NIO Thrift-like payload | **only by a peer that has cleared B5 trust** | client cert via `RootCACustomTrustManager` | +| Management ↔ cluster peer | NIO payload | **only by a peer that has cleared B4 trust** | client cert via `RootCACustomTrustManager` | +| Console proxy URL | encrypted token (containing VM identity + endpoint + duration) | **yes** | token MUST decrypt + verify with `ConsoleProxyPasswordBasedEncryptor` keys *(documented: `ConsoleProxyPasswordBasedEncryptor.java`)* | +| Secondary-storage HTTP download URL | UUID-named symlink path | **yes** | **no auth on the download link**; the UUID format is the anti-enumeration control and the symlink is removed after a period — timed availability of the download token is the mitigation *(maintainer: vishesh92, DaanHoogland)* | +| Template / ISO upload | URL of remote source | **yes** within RBAC | upload-gated by `registerTemplate` RBAC; bytes are then served to hypervisors as image data | +| User-data / metadata service (`169.254.169.254` from inside guests) | guest-controlled bytes (the request) | **yes from the guest**, but the service is reached *from the guest* and serves only that guest's data | guest-VM-side isolation by virtual router | +| Hypervisor agent log / event stream | bytes from hypervisor | trusted operator surface | none — assumed truthful | +| LDAP / SAML / OAuth response (B6) | bytes from IdP | trusted | LDAP queries treat returned attributes as authoritative | +| Storage response (B8) | bytes / metadata from storage | trusted | bytes are object content; envelope is control-plane | + +### Size / shape / rate + +- CloudStack does not document a maximum signed-API request size; assumed + to be servlet-container default (Jetty / Tomcat) *(inferred — §14 Q21)*. +- API rate limiting is per-account via the global config knobs `api.throttling.*` + *(inferred — §14 Q22)*; an attacker with a valid API key can be rate- + limited at the application layer. +- Template / ISO upload size is bounded by storage capacity and per-account + resource limits *(inferred — §14 Q22)*; pathological compressed-image + inputs (e.g. extremely compressible QCOW2 with sparse holes that expand + to TB on extraction) are robustness concerns *(inferred — §14 Q23)*. +- Cluster-peer and agent RPC payload sizes: no documented application-layer + cap; NIO framing applies *(inferred — §14 Q21)*. + +## §7 Adversary model + +### Actors + +| Actor | In scope? | Capabilities granted | +| --- | --- | --- | +| Unauthenticated network peer reaching `:8080`/`:8443` | **yes** | TCP to the listening ports; may attempt authentication; may attempt to violate the protocol pre-auth | +| Unauthenticated peer reaching `:8250` (agent port) | **only if** `ca.plugin.root.auth.strictness = false`, which on new setups it is **not** (default `true`); `false` arises only on un-remediated pre-Aug-2017 upgrades (§5a) | TCP to the listening port; may attempt to connect as a peer without presenting a cert | +| Unauthenticated peer reaching `:8096` (integration port) | **yes** *if* the port is open (typically not in production) | full unauthenticated admin API | +| Authenticated end user with limited RBAC role | **yes** | call APIs their role permits; manage VMs/networks/storage in their domain/account/project | +| Authenticated end user with broad RBAC role | partial | only RBAC-envelope escapes are in scope | +| Authenticated domain admin | **yes** | full management within their domain; cross-domain leakage is in scope | +| Authenticated root admin | **out of scope** — see §3 item 4 | unbounded by design | +| Co-tenant (different account in same domain or different domain on same CloudStack) | **yes** | cross-tenant leakage (VM ID guessing, network bleed, storage bleed, template visibility) is in scope | +| Guest VM workload | **partial** | hypervisor-mediated; out-of-scope for hypervisor isolation bugs (§3 item 5), in-scope for the orchestration that placed the VM (security-group rule application, VLAN tagging, public IP routing) | +| Browser holding a valid console-proxy URL | **yes** | the URL is a bearer credential; scope of harm is one VM's console for the URL's lifetime | +| Operator | **out of scope** | see §3 item 1 | +| Hostile hypervisor | **out of scope** | see §3 item 3 | +| Hostile LDAP / SAML / OAuth IdP, hostile NSX/Netscaler/Tungsten, hostile S3 endpoint | **out of scope** | see §3 item 2 | +| Reverse proxy that should be trusted but is not in `proxy.cidr` | **out of scope** | its forward headers are not honoured | +| Local process on the management-server host running as a different UID | **partial** *(inferred — §14 Q24)* | same-host attackers with non-cloudstack UID can reach `:8080` unless host firewalling forbids; CloudStack does not defend against same-host `root` | +| Side-channel observer (cache timing, network timing, hypervisor side channels) | **out of scope** *(inferred — §14 Q25)* | n/a | +| Quantum adversary | **out of scope** | n/a | + +### Authenticated-but-Byzantine peer (distributed-systems threshold) + +CloudStack is **not** a Byzantine-fault-tolerant system. A compromised +management-server cluster peer with a valid Root-CA-issued cert can +schedule arbitrary work onto the agent fleet, read any guest's data, and +hand out console-proxy tokens. The cluster trusts its own membership +*(inferred — §14 Q26)*. Likewise, a compromised agent host can serve +malicious data on the management network and produce wrong status. → +reports requiring a Byzantine internal peer are `OUT-OF-MODEL: +adversary-not-in-scope`. + +## §8 Security properties the project provides + +For each property: condition, violation symptom, severity tier, provenance. + +### P1 — Authentication of API clients via signed request + +- **Condition**: a request carries `apiKey` + `signature` (and, for + signature version 3, an `expires` parameter not in the past) + *(documented: `ApiServer.java` `verifyRequest`)*; the signature is + HMAC-SHA1 of the canonical parameter string under the per-user + secret key, base64-encoded, lowercased, URL-decoded, and compared + to the computed value using `ConstantTimeComparator.compareStrings` + *(documented: same file line 1137)*. +- **Violation symptom**: a request executes API commands without a + valid `apiKey`+`signature` pair (and without a valid session + cookie / SAML / OAuth / LDAP login). +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented)* + +### P2 — Session authentication via password + optional 2FA + +- **Condition**: user logs in via the `login` API; 2FA is verified after + password if enabled for the user / domain *(documented: `ApiServlet.java` + lines 360–582)*. +- **Violation symptom**: a session is created without a valid password, or + 2FA enforcement is bypassed for a user where it is mandated. +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented)* + +### P3 — Constant-time signature comparison + +- **Condition**: applies to the API signature check. +- **Violation symptom**: timing-side-channel measurement of signature + comparison reveals the expected signature byte-by-byte. +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented: `ApiServer.java` line 1137)* + +### P4 — Authorization via RBAC + domain/account/project hierarchy + +- **Condition**: the authenticated principal calls an API command, and the + command name is permitted for their role *(documented: `plugins/acl/{static,dynamic,project}-role-based`)*; + resources named in the request belong to the principal's domain/account/project + or to a child within the principal's scope. +- **Violation symptom**: a non-root principal successfully executes an + API command not licensed for their role, or operates on a resource + outside their domain/account/project scope. +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented)* + +### P5 — Mutual TLS on management ↔ agent, management ↔ cluster peer, *when configured* + +- **Condition**: `ca.plugin.root.auth.strictness = true` — **the default + on new setups** *(maintainer: vishesh92 — + `https://github.com/apache/cloudstack/pull/2239`)*. Pre-Aug-2017 + upgrades may leave it `false` until the documented upgrade step is + applied *(maintainer: DaanHoogland)*. `ca.plugin.root.allow.expired.cert` + remains `true` (cert-rotation concession), so the property covers + *peer-cert presence and Root-CA chain*, not cert freshness. +- **Violation symptom**: a peer without a Root-CA-issued cert successfully + completes a session on `:8250` or the cluster port on a setup where + strictness is on. +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented; default resolved by maintainer.)* + +### P6 — Reverse-proxy IP-trust gating for forward headers + +- **Condition**: `proxy.header.verify` on (default `false`) *(maintainer: + vishesh92 — §14 Q17)*; + only requests whose `Remote_Addr` falls in `proxy.cidr` have their + `proxy.header.names` forward header(s) consulted *(documented: + `ApiServlet.java` `getClientAddress` `NetUtils.isIpInCidrList`; setting + names maintainer: vishesh92)*. +- **Violation symptom**: a request from a source IP **outside** + `proxy.cidr` succeeds with an attacker-supplied forward header + taking effect. +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented)* + +### P7 — Console-proxy token confidentiality and integrity + +- **Condition**: tokens are encrypted under the + `ConsoleProxyPasswordBasedEncryptor` keys *(documented: + `ConsoleProxyPasswordBasedEncryptor.java`)*; a token includes the VM + identity, the hypervisor endpoint, and a duration / expiry. +- **Violation symptom**: a third party with no console-access RBAC + privilege forges or decrypts a token to gain console access; or a token + remains valid past its declared expiry. +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented)* + +### P8 — Application-secret encryption at rest in the DB via JaSypt + +- **Condition**: `security.encryption.key` + `security.encryption.iv` are + initialised at first boot and kept under filesystem ACLs + *(documented: `framework/security/.../KeysManager.java`, + `README.md` Cryptographic Software notice)*. +- **Violation symptom**: an attacker with read access to the DB but not + to the encryption key file recovers plaintext for secrets the model + claims are encrypted (typically: external service passwords, account + API secret keys when stored encrypted). +- **Severity**: **security-critical**, `VALID` per §13. +- *(documented)* + +### P9 — Memory safety on well-formed inputs across documented surfaces (JVM-bounded) + +- **Condition**: input matches the documented protocol on B1–B5; the JVM + is conformant; native code is invoked only via documented hypervisor + SDKs (libvirt / vSphere / XenAPI). CloudStack presumes **no limitation + on implementation language** — ocaml, python and bash run on hypervisors + and go is used on the management server (the set may grow); the + memory-safety claims here hold for the **JVM components**, to which the + JVM-conformance condition applies *(maintainer: DaanHoogland — §14 Q27)*. +- **Violation symptom**: heap corruption, OOM-via-input-size attack on a + surface where the input source is `:8080` / `:8443` / B5; JVM-side + crashes from a request a normally-RBAC'd user could send. +- **Severity**: **security-critical** when reachable from network input; + **`VALID-HARDENING`** when reachable only by a writer who already + controls the bytes (§3 item 5). +- *(maintainer: DaanHoogland — §14 Q27)* + +### P10 — Bounded RBAC scope of cross-domain visibility (`SHOW`-equivalent listing) + +- **Condition**: `list*` API commands filter responses to the principal's + domain/account/project scope per `plugins/acl/` policy. +- **Violation symptom**: a `list*` response leaks resource IDs / names / + metadata for resources outside the principal's RBAC scope. +- **Severity**: **security-critical** for resources whose existence is + itself confidential (typically: customer VM names, custom template + names); `VALID` per §13. +- *(inferred — §14 Q28)* + +## §9 Security properties the project does *not* provide + +State each plainly so a triager can route an inbound report to the matching +disclaimer. + +- **No defence against the operator.** Anyone with root on a + management-server host, the JCEKS keystore + `security.encryption.key`, + the Root CA private key, or the MariaDB credentials wins. See §3 item 1 + *(inferred — §14 Q6)*. +- **No defence against a malicious external service the operator + configured.** A hostile LDAP/SAML/OAuth IdP, NSX controller, Tungsten, + Netscaler, S3 endpoint, or backup provider is trusted. See §3 item 2. +- **No defence against the hypervisor.** Guest VM escape via libvirt, + vSphere, XenAPI, Hyper-V is upstream. See §3 item 3. +- **No isolation between a root admin's API call and the management-server + process.** Root admin can register arbitrary plugins, upload arbitrary + templates, run `runCustomAction`. See §3 item 4 *(inferred — §14 Q8)*. +- **No sandbox for guest VM workloads beyond what the hypervisor provides.** + Side-channel leaks between co-tenant VMs (cache, branch, memory bus, + shared GPU) are out of scope. See §3 item 5 *(inferred — §14 Q9)*. +- **No sandbox for user-data / templates / ISOs.** Templates run as their + own OS image with their own cloud-init; CloudStack does not parse or + reject user-data semantics. See §3 item 6 *(inferred — §14 Q10)*. +- **No defence against decompression / decoding bombs in uploaded + templates / ISOs.** A pathological QCOW2 / RAW image can consume + arbitrary CPU / disk on extraction; per-account resource limits are the + bound *(inferred — §14 Q23)*. +- **No defence against intra-cluster Byzantine failure.** A compromised + cluster peer with a valid Root-CA-issued cert can read any data the + cluster can read; see §7 *(inferred — §14 Q26)*. Likewise a compromised + agent host. +- **No data-at-rest encryption beyond JaSypt for selected DB columns + + whatever storage layers provide.** Guest volumes are encrypted only if + the primary-storage plugin supports it (Ceph RBD encryption, LUKS at + hypervisor layer) and the operator has configured it *(inferred — + §14 Q29)*. +- **No defence against side-channel observation** of API request timing, + agent RPC timing, or memory access patterns *(inferred — §14 Q25)*. +- **No application-layer constant-time comparison of anything other than + the API signature.** Login password comparison, session cookie + comparison, console-token comparison — not documented constant-time + *(inferred — §14 Q30)*. +- **No defender stance against an attacker on the same Linux host running + as a non-`cloudstack` UID** — CloudStack defends only across the + network surface; same-host attackers with shell access on the + management-server host already have many paths to win *(inferred — + §14 Q24)*. +- **No supported posture for the integration API port (`:8096`).** When + open, it is an unauthenticated admin surface; closing it is the + operator's job *(inferred — §14 Q20)*. + +### False-friend properties (call out separately) + +- **The Root CA is self-signed and auto-generated** — it is *not* a + publicly-trusted CA. Browsers and external clients require manual trust + bootstrap. The Root CA private key resides on the management server; a + compromised management server compromises the entire agent fleet's + trust. +- **`ca.plugin.root.auth.strictness = false` is not "TLS off" — it is + "client cert not required"** *(documented: `RootCAProvider.java`)*. TLS + on the wire is still there; what is missing is the peer-cert check. + Note the value is `true` on new setups *(maintainer: vishesh92)*; a + scanner that flags "client cert not requested" is only correct on an + un-remediated pre-Aug-2017 upgrade, and even then it identifies a + documented upgrade step, not a transport-encryption bug. +- **`ca.plugin.root.allow.expired.cert = true` is the operational default + to survive cert-rotation lag** but is not a security boundary. +- **The HMAC-SHA1 signature is request-integrity over the URL, not + request encryption.** Transport encryption is TLS; if the operator + serves the API over `http://`, the signature still validates but the + whole request (including the secret-derived signature) is visible to + the network. +- **The console-proxy URL is a bearer credential.** Anyone who sees the + URL (in logs, in a proxy, in a shoulder-surf) holds the console for + the URL's lifetime. +- **`list*` filtering is a per-call authorization view, not an + information-flow channel.** Existence of a resource that the principal + cannot see may leak through error messages, async-job status, event + logs, or by-ID lookup probing *(inferred — §14 Q28)*. +- **The integration API port is not a "trusted" port in the sense of + Kerberos `auth-int` — it is *no authentication at all***. The name + invites confusion. +- **JaSypt-encrypted DB columns are *(documented)* protected against a + DB-only read.** They are *not* protected against an attacker who + obtains both the DB and the encryption-key file. + +### Well-known attack classes the project does not defend against + +- **Cross-tenant VM-ID guessing / template-name enumeration**: §10 misuse, + not engine breakage. +- **Decompression / decoding bombs in uploaded templates and ISOs**. +- **Hypervisor side-channel attacks between co-tenant VMs**. +- **Confused-deputy between RBAC role and resource ownership** — e.g. a + domain admin's role permits a command, but the resource named is in a + child domain they should not touch *(inferred — §14 Q28)*. +- **Time-of-check-to-time-of-use** between RBAC check at API entry and + the actual orchestration on the agent fleet — policy revocations + mid-job are not retroactively enforced *(inferred — §14 Q31)*. + +## §10 Downstream responsibilities + +The operator deploying CloudStack in production **must**: + +1. Keep `ca.plugin.root.auth.strictness = true` (the default on new + setups). When **upgrading from a pre-Aug-2017 version**, follow the + documented upgrade step to turn strictness on — otherwise agent and + cluster-peer ports accept peers without a cert *(maintainer: vishesh92, + DaanHoogland — `https://github.com/apache/cloudstack/pull/2239`)*. + Consider tightening `ca.plugin.root.allow.expired.cert` (default `true`) + once cert rotation is reliable. +2. Restrict the management network at L2/L3 so that `:8250` (agent), + `:9090` (cluster), and the MariaDB port are reachable only from the + intended peers *(inferred — §14 Q13)*. +3. Restrict the integration API port `:8096` — either disable it entirely + or limit it to a localhost/management subnet *(inferred — §14 Q20)*. +4. Terminate TLS for the JSON API and Web UI on `:8443` (not `:8080`); if + `:8080` is exposed at all, only behind a TLS-terminating reverse + proxy *(inferred — §14 Q32)*. +5. When using a reverse proxy, set `proxy.header.verify = true`, + `proxy.header.names` to the forward header(s) the proxy sets, *and* + `proxy.cidr` to the proxy's CIDR — leaving `proxy.cidr` unset/empty + means the header is ignored (safe-default per P6), but a misconfigured + wide CIDR is a trust-bypass *(setting names maintainer: vishesh92)*. +6. Protect the `security.encryption.key` / `security.encryption.iv` + files, the JaSypt-encrypted DB, the Root CA private key, and the + `cloudstack-management` Unix user's home directory at OS level. +7. Keep the password-encoder configuration at safe defaults: + `user.password.encoders.order` defaults to + `PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT` (so PBKDF2 is used to hash + new passwords) and `user.password.encoders.exclude` defaults to + `MD5,LDAP,PLAINTEXT` (so the weak encoders are not chosen for hashing, + only retained for verifying already-stored hashes) *(maintainer: + vishesh92)*. Do not remove `MD5`/`PLAINTEXT` from the exclude list in + production — the supported greenfield encoder set is + `PBKDF2,SHA256SALT,SAML2` *(maintainer: vishesh92 — §14 Q19)*. +8. Enable 2FA (`totp` or `static-pin`) for administrators and ideally for + all users — 2FA on/off is a deployment choice via `enable.user.2fa` + and `mandate.user.2fa` (both default `false`) *(maintainer: vishesh92 — + §14 Q18)*. +9. Rotate per-user API secret keys on personnel change and on suspected + compromise. +10. Treat user-uploaded templates and ISOs as crossing a trust boundary — + scan / quarantine before allowing into the supported-template set. +11. Apply per-account resource limits (vCPU / RAM / volume size / image + size) to bound decompression-bomb and orchestration-DoS attacks. +12. Configure storage-layer encryption (Ceph RBD encryption, LUKS at KVM, + vSphere VM Encryption, etc.) if data-at-rest encryption is required. +13. Secure each `cloudstack-agent` host: `cloudstack` Unix user, agent + keystore under `/etc/cloudstack/agent/`, root account, libvirt / + vSphere admin credentials. +14. Restrict console-proxy URLs: do not log them, do not embed them in + public responses, set a short token lifetime. +15. Audit API call logs (via the event-bus plugin) for anomalous patterns. + +## §11 Known misuse patterns + +- **Leaving `:8250` open to the world with `ca.plugin.root.auth.strictness=false` + on an upgraded pre-Aug-2017 cluster.** New setups default to `true`; + the `false` value only survives an upgrade where the documented step + was skipped *(maintainer: vishesh92, DaanHoogland)*. In that state any + peer can connect as an agent — an upgrade-hygiene gap, dispositioned + `OUT-OF-MODEL: non-default-build` (documented upgrade step not applied). +- **Exposing `:8096` (integration API) publicly.** Anyone reaching the + port executes admin API commands without auth. +- **Exposing `:8080` (HTTP JSON API) publicly without a TLS-terminating + reverse proxy.** Signed-request integrity holds, but the API secret- + key-derived signature is visible to any wire observer; replay within + the `expires` window is trivial. +- **Setting `proxy.header.verify=true` with `proxy.cidr` wider than + the actual reverse-proxy CIDR.** An attacker outside the proxy can + spoof a `proxy.header.names` header and claim any IP address for audit + logs and authentication-IP checks *(setting names maintainer: vishesh92)*. +- **Removing `MD5`/`PLAINTEXT` from `user.password.encoders.exclude` (or + reordering them to the front of `user.password.encoders.order`) in + production.** The encoders ship for verifying legacy hashes; promoting + them to hash new passwords stores weakly-protected credentials + *(maintainer: vishesh92 — §14 Q19; the supported greenfield encoder set is `PBKDF2,SHA256SALT,SAML2`)*. +- **Granting domain admin to too many users.** A domain admin can manage + all accounts within the domain — including reading guest console URLs. +- **Embedding console-proxy URLs in screenshots, ticketing systems, or + chat.** Tokens are bearer credentials. +- **Reusing `security.encryption.key` across environments of different + trust levels.** A staging-env leak becomes a production-env decrypt + primitive *(inferred — §14 Q33)*. +- **Leaving `ca.plugin.root.auth.strictness=false` after a pre-Aug-2017 + upgrade in a multi-management-server deployment.** A peer can join the + cluster without a cert until the documented upgrade step flips it to the + new-setup default of `true` *(maintainer: vishesh92, DaanHoogland)*. +- **Uploading large or pathological templates and relying on hypervisor + to enforce size.** Per-account resource limits, not the engine, are the + enforcement. + +## §11a Known non-findings (recurring false positives) + +This section is the highest-leverage input for automated agentic security +scans. Each entry: tool symptom, why it is safe under the model, the § +that licenses the call. + +- **"Management ↔ agent port `:8250` accepts no client cert" reported + against a setup with `ca.plugin.root.auth.strictness=false`.** New setups + default to `true` and **do** require a Root-CA-signed client cert + *(maintainer: vishesh92 — `https://github.com/apache/cloudstack/pull/2239`)*. + The value is `false` only on an upgrade from a pre-Aug-2017 version that + predates the setting, and the upgrade instructions document turning it on + *(maintainer: DaanHoogland)*. → On a new install: `KNOWN-NON-FINDING` + (strictness is on). On an upgraded install with the step skipped: + `OUT-OF-MODEL: non-default-build` (documented upgrade step not applied). +- **"Integration port `:8096` is unauthenticated."** The port is + unauthenticated by design; operator responsibility per §10 to close / + bind to localhost. → `OUT-OF-MODEL: non-default-build` once the PMC + confirms. +- **"HMAC-SHA1 signature uses SHA1."** SHA1-HMAC is **not** broken for + HMAC use; collision attacks on SHA1 do not extend to HMAC-SHA1 + *(documented: cryptographic literature; CloudStack uses + `Mac.getInstance("HmacSHA1")` — `ApiServer.java` line 1130)*. → `KNOWN-NON-FINDING`. +- **"Constant-time string compare for the signature."** Already done — + `ConstantTimeComparator.compareStrings` per `ApiServer.java` line 1137. + → `KNOWN-NON-FINDING` (a finding flagging this is wrong). +- **"Root CA private key is on the management server."** By design — the + management server *is* the CA. → `BY-DESIGN: property-disclaimed`. +- **"Self-signed Root CA cert."** By design — the CA is generated at + first boot per `RootCAProvider.java`. Browsers will warn until the + operator bootstraps trust. → `BY-DESIGN: property-disclaimed`. +- **"Expired agent cert is accepted (`ca.plugin.root.allow.expired.cert=true`)."** + Documented default — an operational concession to cert-rotation lag, paired + with the strictness default *(maintainer: vishesh92, DaanHoogland)*. → + `VALID-HARDENING` at most; tightening it is an operator choice per §10. +- **"Hardcoded password / keytab in `tools/marvin/`, `test/`, `developer/`, + `quickcloud/`."** These directories are unsupported components per §3 + item 7. → `OUT-OF-MODEL: unsupported-component`. +- **"User-data / template contents execute arbitrary code in the guest + VM."** Templates are run as their own OS by the hypervisor; cloud-init + / user-data is intentionally a code-execution channel into the guest. + → `BY-DESIGN: property-disclaimed` per §9. +- **"Root admin can change global config / register plugins / upload + arbitrary templates."** Documented and intentional. → `BY-DESIGN: + property-disclaimed` per §9 / §3 item 4. +- **"DoS via expensive list call on a large CloudStack."** Pagination is + present; further bounds are admission-control / quota. → `BY-DESIGN: + property-disclaimed` per §9. +- **"Decompression bomb in an uploaded QCOW2 / template."** Per-account + resource limits are the bound. → `VALID-HARDENING` at most, unless the + decompression reaches §8 P9 memory-safety violations. +- **"Vendored Bouncy Castle / JaSypt / noVNC / `pako` has CVE-X."** Report + upstream; `systemvm/agent/noVNC` is a vendored fork of + `github.com/novnc/novnc` with CloudStack changes, and there is no + automated sync procedure today *(maintainer: vishesh92, DaanHoogland)*. → + `OUT-OF-MODEL: unsupported-component` (upstream pointer); a + CloudStack-introduced change *to* the fork is in-model. +- **"Secondary-storage download URL has no authentication / can be replayed."** + By design: download links are UUID-named symlinks served by an Apache + httpd with no auth on the link; the UUID format defeats enumeration and + the symlink is removed after a period, so timed availability is the + mitigation *(maintainer: vishesh92, DaanHoogland)*. → `BY-DESIGN: + property-disclaimed` for the no-auth aspect; a link that is *not* removed + after its window, or a guessable (non-UUID) name, is `VALID-HARDENING`. +- **"A proxy-set forward header is honoured without authentication."** + Honoured only if (a) `proxy.header.verify=true`, (b) the header is one of + `proxy.header.names`, *and* (c) the connecting `Remote_Addr` ∈ + `proxy.cidr` *(setting names maintainer: vishesh92)*. → `KNOWN-NON-FINDING`. +- **"Session-fixation: a session ID is reusable after failed login."** + `invalidateHttpSession` is called on each auth failure path per + `ApiServlet.java`. → `KNOWN-NON-FINDING` (verify the symptom; if + reproducible, escalate to `MODEL-GAP`). + +## §12 Conditions that would change this model + +Revise this document when any of the following lands: + +- A new authentication mechanism on a client-facing surface (e.g. + mTLS-as-API-auth on the JSON API, WebAuthn, OIDC). +- A new RBAC backend beyond the three included ACL plugins (e.g. OPA + integration, policy-engine integration). +- A new data-at-rest encryption story at the CloudStack layer (currently + delegated; see §9). +- A change in the default of any §5a flag, *especially* + `ca.plugin.root.auth.strictness` and `ca.plugin.root.allow.expired.cert`. +- Removal or change of the legacy `md5` / `plain-text` user-authenticator + plugins. +- A change in the signing algorithm or signature scheme on the JSON API + (e.g. SHA1 → SHA256 by default). +- A new hypervisor or system VM that adds a new trust boundary. +- A change in the extension mechanisms implemented by CloudStack + *(maintainer: DaanHoogland — §14 Q36)*. +- A new external-data surface (a new SDN controller integration, a new + storage provider, a new backup provider). +- A vulnerability report that cannot be cleanly routed to one of the §13 + dispositions: that is evidence the model is incomplete. + +## §13 Triage dispositions + +A report against `apache/cloudstack` receives exactly one of the +following: + +| Disposition | Meaning | Licensed by | +| --- | --- | --- | +| `VALID` | Violates a §8 property via an in-scope §7 adversary using an in-scope §6 input. | §8, §6, §7 | +| `VALID-HARDENING` | No §8 property violated, but a §11 misuse pattern can be made harder to fall into by code change. Fixed at maintainer discretion, typically no CVE. | §11 | +| `OUT-OF-MODEL: trusted-input` | Requires attacker control of a §6 parameter the model marks trusted (e.g. operator-supplied config flag, hostile LDAP/SAML/NSX/etc.). | §6 | +| `OUT-OF-MODEL: adversary-not-in-scope` | Requires a §7 actor the model excludes (operator, hostile hypervisor, hostile external IdP / SDN, Byzantine peer, side-channel observer, same-host non-`cloudstack` `root`). | §7 | +| `OUT-OF-MODEL: unsupported-component` | Lands in `tools/marvin/`, `test/`, `developer/`, `quickcloud/`, vendored upstream code, `simulator` hypervisor, etc. | §3 items 7–8 | +| `OUT-OF-MODEL: non-default-build` | Only manifests under a §5a flag that is not the new-setup default (e.g. `ca.plugin.root.auth.strictness=false` surviving an un-remediated pre-Aug-2017 upgrade, integration port `:8096` open). | §5a | +| `OUT-OF-MODEL: equivalent-harm` | An actor already-authorized under the model can cause the same harm via a documented path (root admin doing root-admin things, RBAC-licensed user using their RBAC-licensed commands). | §3 items 4, 5 | +| `BY-DESIGN: property-disclaimed` | Concerns a §9 property the project explicitly does not provide (template sandboxing, side-channel resistance, hypervisor isolation, etc.). | §9 | +| `KNOWN-NON-FINDING` | Matches a §11a recurring false positive. | §11a | +| `MODEL-GAP` | Cannot be cleanly routed to any of the above — triggers §12 model revision. | §12 | + +## §14 Open questions for the maintainers + +Every *(inferred)* tag in the body maps to one of these. Proposed answers +are inline; please confirm, correct, or strike. + +### Wave 1 — scope, intended use, the two big insecure defaults + +**Q1.** ~~The model assumes CloudStack is "a clustered distributed +control plane deployed inside an operator-controlled datacenter +network", not a single-host appliance or a hosted SaaS. Confirm?~~ +**RESOLVED** *(maintainer: DaanHoogland)* — distributed control plane; +**both** a single management-server instance (smaller clouds) and a +clustered deployment are supported topologies. Folded into §2. + +**Q2.** ~~Are the SecondaryStorageVM, ConsoleProxyVM, and Virtual Router +treated as trusted-once-enrolled peers, or do they get their own trust +tier?~~ **RESOLVED** *(maintainer)* — **yes**, same trust tier as agents, +not a separate tier. Folded into §2 caller-roles. + +**Q3.** ~~Are external integrations (LDAP, SAML2 IdP, OAuth2 IdP, NSX +controller, Netscaler, Tungsten, S3-compatible storage, backup +providers) modeled as trusted control-plane peers?~~ **RESOLVED** +*(maintainer: DaanHoogland — yes)* — trusted control-plane peers; this +licenses §3 item 2 and §11a trusted-input dispositions. *(maps to §2, §3, §11a)* + +**Q4.** ~~SecondaryStorageVM HTTP download surface — is the URL token +per-template ACL-checked, or is the SSVM URL itself a bearer credential?~~ +**RESOLVED** *(maintainer: vishesh92, DaanHoogland)* — download links are +UUID-named symlinks served by an Apache httpd with **no auth on the link**; +the UUID format defeats enumeration and the symlink is removed after a +period (timed availability is the mitigation). The PMC noted this should +be re-tested/confirmed in code. Folded into §6, §11a. *(Daan also asked +why static code analysis did not surface this — a note for the scan +agent, not a model gap.)* + +**Q5.** ~~Vendored upstream code under `systemvm/agent/noVNC` and bundled +JaSypt / Bouncy Castle / JSch — is the policy "report upstream; we pick up +fixes on next sync"?~~ **RESOLVED** *(maintainer: vishesh92, DaanHoogland)* +— `systemvm/agent/noVNC` is a **vendored fork of `github.com/novnc/novnc`** +with CloudStack changes; vendored bugs go upstream. There is **no automated +update procedure today** (dependabot has not produced viable PRs); the PMC +would prefer to establish one. Folded into §3 item 8, §11a. + +**Q6.** ~~Is "an operator with `root` on a management-server host, the +JCEKS keystore + encryption keys, the Root CA private key, or MariaDB +credentials" out of scope?~~ **RESOLVED** *(maintainer: DaanHoogland — yes)* +— `OUT-OF-MODEL: adversary-not-in-scope`. *(maps to §3 item 1, §9)* + +**Q7.** ~~Hypervisor bugs (libvirt / vSphere SDK / XenAPI / Hyper-V API / +KVM/QEMU itself) — out of scope, report upstream?~~ **RESOLVED** +*(maintainer: DaanHoogland — yes, out of scope; report upstream)*. *(maps to §3 item 3)* + +### Wave 2 — the two big insecure defaults + +**Q12.** ~~**Highest-leverage question in the model.** Are +`ca.plugin.root.auth.strictness` and `ca.plugin.root.allow.expired.cert` +shipped insecure-by-default?~~ **RESOLVED** *(maintainer: vishesh92, +DaanHoogland — `https://github.com/apache/cloudstack/pull/2239`)*: + +- `ca.plugin.root.auth.strictness` defaults to **`true` on new setups** — + the management server **does** require a Root-CA-signed client cert on + `:8250` and the cluster ports. It is `false` **only** after upgrading + from a version released before Aug 2017 that predates the setting; the + upgrade instructions document turning it on, so a leftover `false` is an + upgrade-hygiene gap, not a shipped insecure default. +- `ca.plugin.root.allow.expired.cert` defaults to `true` as an operational + concession to cert-rotation lag. + +This resolution reshaped §3 item 1, §5a, §7 (the un-certed peer row), +§8 P5, §9 false-friends, §10, §11, §11a, and §13. The earlier +"assumes operator must flip per §10" framing is withdrawn. + +### Wave 3 — adjacent insecure defaults and admin-only surfaces + +**Q8.** ~~Is "a root admin with full RBAC role causes harm Y via a +documented path Z" out of scope (proposed: **yes**, `OUT-OF-MODEL: +equivalent-harm`)? In particular: `runCustomAction`, template upload, +plugin registration, global config change, system-VM patching, system-VM +console access.~~ **RESOLVED** *(maintainer: vishesh92)* — yes; a root +admin generally has direct access to most of these resources anyway → +`OUT-OF-MODEL: equivalent-harm`. *(maps to §3 item 4, §9)* + +**Q9.** ~~Guest VM workloads — confirm that hypervisor-mediated side +channels and resource-exhaustion-within-allocation are out of scope, and +that the in-scope orchestration concerns are limited to "did CloudStack +place the VM in the right VLAN / apply the right security group / route +the right IP" (proposed)?~~ **RESOLVED** *(maintainer: vishesh92)* — yes; +side channels + resource-exhaustion-within-allocation are out of scope. +The one in-model case: CloudStack applying a wrong/insecure setting while +launching or managing the guest (CloudStack must use correct/secure +hypervisor settings). *(DaanHoogland to confirm the boundary.)* *(maps to +§3 item 5, §7, §9)* + +**Q10.** ~~Templates / ISOs / user-data — confirm that there is no +sandboxing of user-supplied OS images, and that user-data is intentionally +a code-execution channel into the guest (proposed)?~~ **RESOLVED** +*(maintainer: vishesh92)* — yes; userdata is the end user customizing +their own guest OS (tenant-controlled data inside their own boundary), not +a CloudStack-side injection surface. *(maps to §3 item 6, §9)* + +**Q11.** Confirm the unsupported-component list: `tools/marvin/`, +`test/`, `developer/`, `quickcloud/`, `cloud-cli/`, +`tools/{devcloud4,devcloud-kvm,appliance,checkstyle,transifex,bugs-wiki,...}`, +`simulator` hypervisor plugin. Anything to add or remove? **RESOLVED** *(maintainer: DaanHoogland)* — exclude `simulator` and `tools/appliance` explicitly (out of scope for now; a future security-purpose tooling effort may revisit). *(maps to §3 item 7)* + +**Q17.** Forward-header gating — the **setting names are confirmed** +*(maintainer: vishesh92)*: `proxy.header.verify` (the on/off gate), +`proxy.header.names` (header names to consult), and `proxy.cidr` (CIDRs of +the `Remote_Addr` values for which those headers are honoured). **RESOLVED** *(maintainer: vishesh92)* — `proxy.header.verify` is +**`false` by default**; only when the connecting `Remote_Addr` ∈ +`proxy.cidr` does CloudStack read the client IP from `proxy.header.names`. +*(maps to §5a, §6, §10)* + +**Q18.** ~~2FA — proposed: off by default, operator turns it on per +domain / per user via `enable.2fa.*`. Confirm; and is "2FA disabled in +production" a §10 violation or a deployment choice?~~ **RESOLVED** +*(maintainer: vishesh92)* — deployment choice, not a §10 violation. Two +domain-configurable global settings: `enable.user.2fa` (default `false`; +whether 2FA is enabled) and `mandate.user.2fa` (default `false`; whether +2FA is mandatory — applies only when `enable.user.2fa` is true). *(maps to +§5a, §10)* + +**Q19.** User-authenticator plugins — encoder selection is governed by +`user.password.encoders.order` (default +`PBKDF2,SHA256SALT,MD5,LDAP,SAML2,PLAINTEXT`) and +`user.password.encoders.exclude` (default `MD5,LDAP,PLAINTEXT`), so PBKDF2 +is the effective hashing default and `MD5`/`PLAINTEXT` are retained only +for verifying legacy hashes *(maintainer: vishesh92)*. **RESOLVED** *(maintainer: vishesh92)* — a +report against `md5`/`plain-text` hashing *new* passwords in a greenfield +install is `OUT-OF-MODEL: non-default-build`: the default +`user.password.encoders.exclude` (`MD5,LDAP,PLAINTEXT`) removes them from +the effective set, so the supported greenfield encoders are +`PBKDF2,SHA256SALT,SAML2`. *(maps to §5a, §10, §11)* + +**Q20.** Integration API port `:8096` — proposed: closed (port-zero) by +default in production packaging, open only when explicitly configured; +when open, it is unauthenticated by design. A report of "integration +port allows admin commands without auth" is `OUT-OF-MODEL: +non-default-build` *if* the operator opened it, else `VALID`. Confirm the default. **RESOLVED** *(maintainer: DaanHoogland)* — default is `0` (disabled); `8096` is set only in test configurations. *(maps to §5a, §10, §11a)* + +### Wave 4 — environment, distributed model, false-friends + +**Q13.** Network-fabric assumptions — proposed: at least four logical +networks (management, public, guest, storage), with the management +network as the trusted control plane. Is that the canonical model, or +do you support more compressed topologies (single-fabric) in production? **RESOLVED** *(maintainer: DaanHoogland)* — there are four logical networks (management, public, guest, storage); each may have multiple instances across topologies (e.g. multiple zones) and may be combined within physical networks, but all four logical types must be present for a functional system. *(maps to §5, §10)* + +**Q14.** Clock-skew assumption for signature v3 `expires` enforcement — +proposed: operator's responsibility to keep client + management-server clocks roughly in sync. Confirm. **RESOLVED** *(maintainer: DaanHoogland)* — confirmed; operator responsibility (PMC to add to the security model page). *(maps to §5)* + +**Q15.** Confirm the filesystem-permissions inventory for sensitive +files: JCEKS keystore, Root CA private key, JaSypt key + IV, +`db.properties`. Who owns them, what mode? **RESOLVED** *(maintainer: vishesh92)* — not a CSV inventory of every file in a running system; only the four sensitive artifacts named here (JCEKS keystore, Root CA private key, JaSypt key + IV, `db.properties`). Ownership is `root` (user) / `cloud` (group), mode read+write for the owner and read for the `cloud` group (i.e. `0640`, `root:cloud`). *(maps to §5, §10)* + +**Q16.** Confirm the "what CloudStack does not do to its host" inventory +in §5: no child processes besides agent `Script` invocations / system +VM provisioning; signal-handlers via servlet container default; +environment-variable consumption confined to documented set. Anything to add? **RESOLVED** *(maintainer: DaanHoogland)* — confirmed; nothing to add. *(maps to §5)* + +**Q21.** API request size cap and cluster/agent RPC payload size cap — +are these explicitly bounded, or "whatever Jetty / NIO defaults give"? **RESOLVED** *(maintainer: DaanHoogland)* — the UI server sets an explicit cap, `org.apache.cloudstack.ServerDaemon.DEFAULT_REQUEST_CONTENT_SIZE = 1048576` (1 MiB); for other components the sizes are capped by the upstream components used. *(maps to §6, §9)* + +**Q22.** `api.throttling.*` and per-account resource limits — proposed: +these are the entire DoS-protection surface, with no engine-level guard. Confirm. **RESOLVED** *(maintainer: DaanHoogland)* — confirmed; enforced at the API access check, and `api.throttling.enabled` is **`false` by default**. *(maps to §6, §9, §10)* + +**Q23.** Decompression behaviour on uploaded QCOW2 / RAW / OVA — proposed: +no engine-side cap; per-account storage limits + hypervisor limits are the bound. Confirm. **RESOLVED** *(maintainer: DaanHoogland)* — correct. *(maps to §6, §9)* + +**Q24.** Same-host non-`cloudstack` UID — proposed: game-over, no defence +claimed. Confirm. **RESOLVED** *(maintainer: DaanHoogland, vishesh92)* — same-host non-`cloudstack` UID is game-over (no defence claimed; an operator at that level already controls the host, §3). On the related host-registration question: re-adding a host with the same IP **updates the existing host record** rather than creating a spoofed peer, and is gated by root-admin/operator access plus the keys/certs required to connect to the management server — adding a host directly without those fails (refinement tracked in apache/cloudstack#13182). So this is not an unauthenticated identity-spoof path. *(maps to §7, §9)* + +**Q25.** Side-channel observers (CPU cache timing, branch-predictor / speculative-execution channels e.g. Spectre-class, hypervisor-shared microarchitectural channels) — out of scope (proposed). **RESOLVED** *(maintainer: DaanHoogland)* — agreed, out of scope. *("branch" = branch-predictor / speculative-execution side channels — clarified by producer.)* *(maps to §7, §9)* + +**Q26.** Byzantine-internal-peer threshold — confirm CloudStack makes no +BFT claim, so any compromised cluster peer or agent with a valid +Root-CA-issued cert is unbounded (proposed). **RESOLVED** *(maintainer: DaanHoogland)* — agreed; no BFT claim. (A quorum-style mitigation would only be meaningful in larger clusters, not single/dual-node — possible future feature proposal.) *(maps to §7, §9)* + +**Q27.** §8 P9 memory-safety — JVM-bounded; is the reachability +boundary correctly "in-model for the JSON API + B5 input; out-of-model +for native hypervisor SDK bugs that surface as `Throwable`"? **RESOLVED** *(maintainer: DaanHoogland)* — the reachability boundary is right, but **§8 P9 must not imply CloudStack is Java-only** — no implementation-language limitation is presumed (ocaml, python, bash run on hypervisors; go is used on the management server; the set may grow). The memory-safety claims hold for the JVM components only. *(reflected in §8 P9.)* *(maps to §8 P9, §9)* + +**Q28.** §8 P10 listing-scope — confirm the §10 invariant "`list*` +responses are scoped to the principal's domain/account/project". And: +is information leak via error messages / async-job status / event log an in-model concern, or accepted? **RESOLVED** *(maintainer: DaanHoogland)* — in-model: regular system logs (e.g. log4j) are exempt, but other than those, information leaks (via error messages, async-job status, event log) are a concern. *(maps to §8 P10, §9, §11)* + +**Q29.** Data-at-rest encryption — confirm CloudStack delegates entirely +to storage layer / hypervisor (LUKS, Ceph encryption, vSphere VM +Encryption); no CloudStack-layer encryption of guest volumes. **RESOLVED** *(maintainer: DaanHoogland, vishesh92)* — correct; delegated entirely to the storage layer / hypervisor. *(maps to §9)* + +**Q30.** Constant-time comparison — confirm that *only* the API +signature path uses `ConstantTimeComparator`. Login password compare, +session cookie compare, console-token compare — none documented +constant-time. Is that intentional? **RESOLVED** *(maintainer: DaanHoogland)* — not intentional — the absence of constant-time comparison on the login-password / session-cookie / console-token paths is a lack of feature (hardening opportunity), not a by-design decision. *(maps to §8, §9)* + +**Q31.** Time-of-check-to-time-of-use between RBAC check at API entry +and orchestration on agent fleet — confirm mid-job RBAC revocation is +**not** retroactively enforced (proposed). **RESOLVED** *(maintainer: DaanHoogland)* — agreed/confirmed. *(maps to §9)* + +**Q32.** TLS posture on `:8080` vs `:8443` — confirm production deploys +behind TLS on `:8443` or behind a TLS-terminating reverse proxy; a bare +`:8080` HTTP API is dev-only. **RESOLVED** *(maintainer: DaanHoogland)* — confirmed. *(maps to §5a, §10)* + +**Q33.** `security.encryption.key` reuse across environments — confirm +that reusing the JaSypt key + IV across staging and production is a +documented misuse. **RESOLVED** *(maintainer: DaanHoogland)* — indeed — confirmed misuse. *(maps to §11)* + +### Wave 5 — meta + +**Q34.** Should this document live at `docs/threat-model.md` in +`apache/cloudstack`, or as a page on `cloudstack.apache.org/security/`? +Or both, with one canonical and the other linked? **RESOLVED** *(maintainer: DaanHoogland, vishesh92)* — both: this document is the source of truth, and `cloudstack.apache.org/security` carries an excerpt plus a link to it. *(meta)* + +**Q35.** Is there an existing CloudStack threat-model document +(Confluence, internal, or a `[SECURITY]`-tagged dev@ thread) that this +should reconcile against rather than supersede? **RESOLVED** *(maintainer: DaanHoogland)* — `cloudstack.apache.org/security/` is the only existing security model today; this document becomes its source of truth, with the page linking to it. *(meta — §3.1a of the rubric)* + +**Q36.** What kind of change should trigger a revision (proposed list in +§12 — confirm or correct)? **RESOLVED** *(maintainer: DaanHoogland)* — confirmed, plus add: a change in the extension mechanisms implemented by CloudStack (now reflected in §12). *(meta, §12)* + +**Q37.** §11a is the highest-leverage section for the scan agent's +suppression list. The current draft has 15 patterns; could the PMC +populate §11a from recurring "not a vuln" closures on the +`security@apache.org` ↔ CloudStack triage queue and on +`https://cloudstack.apache.org/security.html`? Concrete asks: 3–5 +patterns the PMC sees recur in inbound reports (e.g. "SSL bare on +`:8080` in a dev cluster", "agent port open without strictness flipped", +"`md5` authenticator left enabled after upgrade", "console URL appears +in support ticket"). *(meta — §11a)* + +**Q38.** Confirm the structural decision to keep the four satellite repos +as separate delta models (`cloudstack-go-threat-model-draft.md`, +`cloudstack-cloudmonkey-threat-model-draft.md`, +`cloudstack-terraform-provider-threat-model-draft.md`, +`cloudstack-kubernetes-provider-threat-model-draft.md`) inheriting §3 +/ §4 / §7 from this document. **RESOLVED** *(maintainer: DaanHoogland)* — confirmed; the satellites are not the system core (the core runs without them, they cannot run without the core), and there is an added hierarchy — `cloudstack-go` is a dependency of the other three. *(meta, §3 item 9)* + +--- + +## Appendix: SECURITY.md → §x back-map + +CloudStack does not currently ship an in-repo `SECURITY.md`; the `README.md` +section "Reporting Security Vulnerabilities" points to +`https://cloudstack.apache.org/security.html` as the canonical disclosure +landing page. The de facto security-policy artifacts are scattered: + +| Source | Claim | Lands in | +| --- | --- | --- | +| `README.md` "Reporting Security Vulnerabilities" | report to `security@apache.org`; canonical page at `cloudstack.apache.org/security.html` | §1 reporting cross-reference | +| `README.md` "Notice of Cryptographic Software" | JaSypt, Bouncy Castle, JSch, OpenSwan, MySQL native encryption | §5 cryptography assumption, §8 P8 | +| `agent/conf/agent.properties` (`host`, `port`, `ssl.handshake.timeout`, …) | agent ↔ management server transport on `:8250` | §2 component table, §4 B5 | +| `server/src/main/java/com/cloud/api/ApiServer.java` `verifyRequest` (lines ~980–1156) | HMAC-SHA1 signature + `expires` enforcement (`enforce.post.requests.and.timestamps`) + constant-time compare | §8 P1, §8 P3, §5a "enforce.post.requests.and.timestamps" row, §11a "SHA1 / constant-time" entries | +| `server/src/main/java/com/cloud/api/ApiServlet.java` `getClientAddress` (lines 700–725) | forward-header gating by `proxy.cidr` / `proxy.header.names` when `proxy.header.verify=true` | §8 P6, §5a "proxy.header.verify" row | +| `server/src/main/java/com/cloud/api/ApiServlet.java` 2FA path (lines 360–582) | password + 2FA flow | §8 P2 | +| `framework/ca/.../CAService.java`, `plugins/ca/root-ca/.../RootCAProvider.java` | Root CA generated at first boot; agent enrolment via `SetupKeyStoreCommand` | §4 B5, §8 P5, §5a strictness/allow-expired rows | +| `plugins/ca/root-ca/.../RootCACustomTrustManager.java` | `authStrictness` and `allowExpiredCertificate` semantics | §5a, §8 P5 | +| `plugins/acl/{static,dynamic,project}-role-based` | RBAC backends | §8 P4 | +| `plugins/user-authenticators/{md5,sha256salted,pbkdf2,plain-text,ldap,saml2,oauth2}` | pluggable user auth; selection via `user.password.encoders.order` / `user.password.encoders.exclude` | §2 caller-roles row, §5a "user.password.encoders.*" rows, §10 item 7 | +| `plugins/user-two-factor-authenticators/{static-pin,totp}` | 2FA backends | §5a "enable.user.2fa / mandate.user.2fa", §10 item 8 | +| `framework/security/.../KeysManager.java`, `KeystoreManager.java` | `security.encryption.key`, `security.encryption.iv` (Hidden), application-secret JaSypt encryption | §8 P8, §5a, §10 item 6 | +| `agent/src/main/java/com/cloud/agent/Agent.java` `setupAgentKeystore` (lines ~793–916) | agent receives Root CA-signed cert via `SetupKeyStoreCommand` and imports it | §4 B5, §8 P5 | +| `server/src/main/java/com/cloud/servlet/ConsoleProxyServlet.java`, `ConsoleProxyPasswordBasedEncryptor.java` | signed encrypted console-proxy URL token | §4 B3, §8 P7 | +| `https://cloudstack.apache.org/security.html` (website) | canonical disclosure landing page | §1 reporting cross-reference (note: not accessible from the producer's network at draft time; verify content with PMC) | diff --git a/agent/bindir/cloud-setup-agent.in b/agent/bindir/cloud-setup-agent.in index 9927c22eebcf..dc4582540df9 100755 --- a/agent/bindir/cloud-setup-agent.in +++ b/agent/bindir/cloud-setup-agent.in @@ -35,7 +35,7 @@ for pythonpath in ( from cloudutils.cloudException import CloudRuntimeException, CloudInternalException from cloudutils.utilities import initLoging, bash -from cloudutils.configFileOps import configFileOps +from cloudutils.configFileOps import configFileOps from cloudutils.globalEnv import globalEnv from cloudutils.networkConfig import networkConfig from cloudutils.syscfg import sysConfigFactory @@ -43,35 +43,41 @@ from cloudutils.serviceConfig import configureLibvirtConfig, configure_libvirt_t from optparse import OptionParser + def getUserInputs(): print("Welcome to the CloudStack Agent Setup:") cfo = configFileOps("@AGENTSYSCONFDIR@/agent.properties") oldMgt = cfo.getEntry("host") - mgtSvr = input("Please input the Management Server Hostname/IP-Address:[%s]"%oldMgt) + mgtSvr = input( + "Please input the Management Server Hostname/IP-Address:[%s]" % oldMgt + ) if mgtSvr == "": mgtSvr = oldMgt try: socket.getaddrinfo(mgtSvr, 443) except: - print("Failed to resolve %s. Please input a valid hostname or IP-Address."%mgtSvr) + print( + "Failed to resolve %s. Please input a valid hostname or IP-Address." + % mgtSvr + ) exit(1) oldToken = cfo.getEntry("zone") - zoneToken = input("Please input the Zone Id:[%s]"%oldToken) + zoneToken = input("Please input the Zone Id:[%s]" % oldToken) if zoneToken == "": zoneToken = oldToken oldPod = cfo.getEntry("pod") - podId = input("Please input the Pod Id:[%s]"%oldPod) + podId = input("Please input the Pod Id:[%s]" % oldPod) if podId == "": - podId = oldToken + podId = oldToken oldCluster = cfo.getEntry("cluster") - clusterId = input("Please input the Cluster Id:[%s]"%oldCluster) + clusterId = input("Please input the Cluster Id:[%s]" % oldCluster) if clusterId == "": clusterId = oldCluster @@ -79,18 +85,20 @@ def getUserInputs(): if oldHypervisor == "": oldHypervisor = "kvm" - hypervisor = input("Please input the Hypervisor type kvm/lxc:[%s]"%oldHypervisor) + hypervisor = input("Please input the Hypervisor type kvm/lxc:[%s]" % oldHypervisor) if hypervisor == "": hypervisor = oldHypervisor try: defaultNic = networkConfig.getDefaultNetwork() except: - print("Failed to get default route. Please configure your network to have a default route") + print( + "Failed to get default route. Please configure your network to have a default route" + ) exit(1) defNic = defaultNic.name - network = input("Please choose which network used to create VM:[%s]"%defNic) + network = input("Please choose which network used to create VM:[%s]" % defNic) if network == "": if defNic == "": print("You need to specify one of Nic or bridge on your system") @@ -100,7 +108,8 @@ def getUserInputs(): return [mgtSvr, zoneToken, network, podId, clusterId, hypervisor] -if __name__ == '__main__': + +if __name__ == "__main__": initLoging("@AGENTLOGDIR@/setup.log") glbEnv = globalEnv() @@ -108,13 +117,23 @@ if __name__ == '__main__': glbEnv.agentMode = "Agent" parser = OptionParser() parser.add_option("-a", action="store_true", dest="auto", help="auto mode") - parser.add_option("-m", "--host", dest="mgt", help="Management server hostname or IP-Address") + parser.add_option( + "-m", "--host", dest="mgt", help="Management server hostname or IP-Address" + ) parser.add_option("-z", "--zone", dest="zone", help="zone id") parser.add_option("-p", "--pod", dest="pod", help="pod id") parser.add_option("-c", "--cluster", dest="cluster", help="cluster id") - parser.add_option("-t", "--hypervisor", default="kvm", dest="hypervisor", help="hypervisor type") + parser.add_option( + "-t", "--hypervisor", default="kvm", dest="hypervisor", help="hypervisor type" + ) parser.add_option("-g", "--guid", dest="guid", help="guid") - parser.add_option("-s", action="store_true", default=False, dest="secure", help="Secure and enable TLS for libvirtd") + parser.add_option( + "-s", + action="store_true", + default=False, + dest="secure", + help="Secure and enable TLS for libvirtd", + ) parser.add_option("--pubNic", dest="pubNic", help="Public traffic interface") parser.add_option("--prvNic", dest="prvNic", help="Private traffic interface") parser.add_option("--guestNic", dest="guestNic", help="Guest traffic interface") @@ -140,15 +159,15 @@ if __name__ == '__main__': glbEnv.pod = userInputs[3] glbEnv.cluster = userInputs[4] glbEnv.hypervisor = userInputs[5] - #generate UUID + # generate UUID glbEnv.uuid = old_config.getEntry("guid") if glbEnv.uuid == "": glbEnv.uuid = bash("uuidgen").getStdout() else: for para, value in list(options.__dict__.items()): if value is None: - print("Missing operand:%s"%para) - print("Try %s --help for more information"%sys.argv[0]) + print("Missing operand:%s" % para) + print("Try %s --help for more information" % sys.argv[0]) sys.exit(1) glbEnv.uuid = options.guid @@ -168,7 +187,7 @@ if __name__ == '__main__': try: syscfg.config() print("CloudStack Agent setup is done!") - except (CloudRuntimeException,CloudInternalException) as e: + except (CloudRuntimeException, CloudInternalException) as e: print(e) print("Try to restore your system:") try: diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index 773ef963ef7d..4e36eff4d75f 100644 --- a/agent/conf/agent.properties +++ b/agent/conf/agent.properties @@ -78,6 +78,14 @@ zone=default # Generated with "uuidgen". local.storage.uuid= +# Enable TLS for image server transfers. The keys are read from: +# cert file = /etc/cloudstack/agent/cloud.crt +# key file = /etc/cloudstack/agent/cloud.key +image.server.tls.enabled=true + +# The Address for the network interface that the image server listens on. If not specified, it will listen on the Management network. +#image.server.listen.address= + # Location for KVM virtual router scripts. # The path defined in this property is relative to the directory "/usr/share/cloudstack-common/". domr.scripts.dir=scripts/network/domr/kvm @@ -209,12 +217,13 @@ hypervisor.type=kvm # the management server would send. # In case of arm64 (aarch64), this will change the machine type to 'virt' and # adds a SCSI and a USB controller in the domain xml. -# Possible values: x86_64 | aarch64 +# Possible values: x86_64 | aarch64 | s390x # If null (default), defaults to the VM's OS architecture #guest.cpu.arch= -# This param will require CPU features on the CPU section. -# The features listed in this property must be separated by a blank space (e.g.: vmx vme) +# Specifies required CPU features for end-user and system VMs. +# These features must be present on the host CPU for VM deployment. +# Multiple features should be separated by whitespace (e.g.: vmx vme). #guest.cpu.features= # Disables memory ballooning on VM guests for overcommit. @@ -307,7 +316,7 @@ iscsi.session.cleanup.enabled=false #vm.migrate.domain.retrieve.timeout=10 # This parameter specifies if the host must be rebooted when something goes wrong with the heartbeat. -#reboot.host.and.alert.management.on.heartbeat.timeout=true +#reboot.host.and.alert.management.on.heartbeat.timeout=false # Enables manually setting CPU's topology on KVM's VM. #enable.manually.setting.cpu.topology.on.kvm.vm=true @@ -363,6 +372,14 @@ iscsi.session.cleanup.enabled=false # to the directory "/usr/share/cloudstack-common/". #network.scripts.dir=scripts/vm/network/vnet +# Sets the VXLAN networking mode used, either 'multicast' (default) or 'evpn'. +# The different modes lead to different scripts being executed by the Agent. +# multicast: modifyvxlan.sh +# evpn: modifyvxlan-evpn.sh +# Existing environments using VXLAN can safely switch to the 'evpn' mode as this +# will not break existing functionality. +#network.vxlan.mode=multicast + # Defines the location for storage scripts. # The path defined in this property is relative. # To locate the script, ACS first tries to concatenate @@ -441,3 +458,45 @@ iscsi.session.cleanup.enabled=false # Wait(in seconds) during agent reconnections. When no value is set then default value of 5s will be used #backoff.seconds= + +# Timeout (in seconds) to wait for the snapshot reversion to complete. +# revert.snapshot.timeout=10800 + +# Timeout (in seconds) to wait for the incremental snapshot to complete. +# incremental.snapshot.timeout=10800 + +# If set to true, creates VMs as full clones of their templates on KVM hypervisor. Creates as linked clones otherwise. +# create.full.clone=false + +# Instance conversion TMPDIR env var +#convert.instance.env.tmpdir= + +# Instance conversion VIRT_V2V_TMPDIR env var +#convert.instance.env.virtv2v.tmpdir= + +# Time, in seconds, to wait before retrying to rebase during the incremental snapshot process. +# incremental.snapshot.retry.rebase.wait=60 + +# Path to the VDDK library directory for VMware to KVM conversion via VDDK, +# passed to virt-v2v as -io vddk-libdir= +#vddk.lib.dir= + +# Ordered VDDK transport preference for VMware to KVM conversion via VDDK, passed as +# -io vddk-transports= to virt-v2v. Example: nbd:nbdssl +#vddk.transports= + +# Optional vCenter SHA1 thumbprint for VMware to KVM conversion via VDDK, passed as +# -io vddk-thumbprint=. If unset, CloudStack computes it on the KVM host via openssl. +#vddk.thumbprint= + +# Timeout (in seconds) for QCOW2 delta merge operations, mainly used for classic volume snapshots, disk-only VM snapshots on file-based storage, and the KBOSS plugin. +# If a value of 0 or less is informed, the default will be used. +# qcow2.delta.merge.timeout=259200 + +# Maximum number of backup validation jobs that can be executed at the same time. Values lower than 0 remove the limit, meaning that as many validations as possible will be done at +# the same time. +# backup.validation.max.concurrent.operations.per.host= + +# Maximum number of backup compression jobs that can be executed at the same time. Values lower than 0 remove the limit, meaning that as many compressions as possible will be +# done at the same time. +# backup.compression.max.concurrent.operations.per.host= diff --git a/agent/conf/developer.properties.template b/agent/conf/developer.properties.template index a70a136f38cd..02f51aa6bb85 100644 --- a/agent/conf/developer.properties.template +++ b/agent/conf/developer.properties.template @@ -5,9 +5,9 @@ # 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 diff --git a/agent/conf/uefi.properties.in b/agent/conf/uefi.properties.in new file mode 100644 index 000000000000..3c8866f634bc --- /dev/null +++ b/agent/conf/uefi.properties.in @@ -0,0 +1,24 @@ +# 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. + +# Configuration file for UEFI + +guest.nvram.template.legacy=@GUESTNVRAMTEMPLATELEGACY@ +guest.loader.legacy=@GUESTLOADERLEGACY@ +guest.nvram.template.secure=@GUESTNVRAMTEMPLATESECURE@ +guest.loader.secure=@GUESTLOADERSECURE@ +guest.nvram.path=@GUESTNVRAMPATH@ diff --git a/agent/pom.xml b/agent/pom.xml index 79e4dbab54b6..5ab6cfe17c13 100644 --- a/agent/pom.xml +++ b/agent/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/agent/src/main/java/com/cloud/agent/Agent.java b/agent/src/main/java/com/cloud/agent/Agent.java index c01f025c6a8e..275fd41edc34 100644 --- a/agent/src/main/java/com/cloud/agent/Agent.java +++ b/agent/src/main/java/com/cloud/agent/Agent.java @@ -26,6 +26,8 @@ import java.net.UnknownHostException; import java.nio.channels.ClosedChannelException; import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -57,7 +59,6 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.ThreadContext; @@ -69,6 +70,8 @@ import com.cloud.agent.api.CronCommand; import com.cloud.agent.api.MaintainAnswer; import com.cloud.agent.api.MaintainCommand; +import com.cloud.agent.api.MigrateAgentConnectionAnswer; +import com.cloud.agent.api.MigrateAgentConnectionCommand; import com.cloud.agent.api.PingAnswer; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.ReadyCommand; @@ -84,6 +87,7 @@ import com.cloud.resource.ServerResource; import com.cloud.utils.NumbersUtil; import com.cloud.utils.PropertiesUtil; +import com.cloud.utils.StringUtils; import com.cloud.utils.concurrency.NamedThreadFactory; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.exception.NioConnectionException; @@ -337,9 +341,8 @@ public void start() { logger.info("Attempted to connect to the server, but received an unexpected exception, trying again...", e); } } - shell.updateConnectedHost(); + shell.updateConnectedHost(((NioClient)connection).getHost()); scavengeOldAgentObjects(); - } public void stop(final String reason, final String detail) { @@ -449,22 +452,30 @@ private void scheduleCertificateRenewalTask() { certExecutor.schedule(new PostCertificateRenewalTask(this), 5, TimeUnit.SECONDS); } - private void scheduleHostLBCheckerTask(final long checkInterval) { + private void scheduleHostLBCheckerTask(final String lbAlgorithm, final long checkInterval) { String name = "HostLBCheckerTask"; if (hostLbCheckExecutor != null && !hostLbCheckExecutor.isShutdown()) { + logger.info("Shutting down the preferred host checker task {}", name); hostLbCheckExecutor.shutdown(); try { if (!hostLbCheckExecutor.awaitTermination(1, TimeUnit.SECONDS)) { hostLbCheckExecutor.shutdownNow(); } } catch (InterruptedException e) { - logger.debug("Forcing {} shutdown as it did not shutdown in the desired time due to: {}", + logger.debug("Forcing the preferred host checker task {} shutdown as it did not shutdown in the desired time due to: {}", name, e.getMessage()); hostLbCheckExecutor.shutdownNow(); } } if (checkInterval > 0L) { - logger.info("Scheduling preferred host task with host.lb.interval={}ms", checkInterval); + if ("shuffle".equalsIgnoreCase(lbAlgorithm)) { + logger.info("Scheduling the preferred host checker task to trigger once (to apply lb algorithm '{}') after host.lb.interval={} ms", lbAlgorithm, checkInterval); + hostLbCheckExecutor = Executors.newSingleThreadScheduledExecutor((new NamedThreadFactory(name))); + hostLbCheckExecutor.schedule(new PreferredHostCheckerTask(), checkInterval, TimeUnit.MILLISECONDS); + return; + } + + logger.info("Scheduling a recurring preferred host checker task with host.lb.interval={} ms", checkInterval); hostLbCheckExecutor = Executors.newSingleThreadScheduledExecutor((new NamedThreadFactory(name))); hostLbCheckExecutor.scheduleAtFixedRate(new PreferredHostCheckerTask(), checkInterval, checkInterval, TimeUnit.MILLISECONDS); @@ -538,6 +549,10 @@ protected boolean cancelStartupTask() { } public void sendStartup(final Link link) { + sendStartup(link, false); + } + + public void sendStartup(final Link link, boolean transfer) { final StartupCommand[] startup = serverResource.initialize(); if (startup != null) { final String msHostList = shell.getPersistentProperty(null, "host"); @@ -545,6 +560,7 @@ public void sendStartup(final Link link) { for (int i = 0; i < startup.length; i++) { setupStartupCommand(startup[i]); startup[i].setMSHostList(msHostList); + startup[i].setConnectionTransferred(transfer); commands[i] = startup[i]; } final Request request = new Request(id != null ? id : -1, -1, commands, false, false); @@ -607,6 +623,46 @@ public Task create(final Task.Type type, final Link link, final byte[] data) { return new ServerHandler(type, link, data); } + protected void reconnect(final Link link) { + reconnect(link, null, false); + } + + protected void reconnect(final Link link, String preferredMSHost, boolean forTransfer) { + if (!(forTransfer || reconnectAllowed)) { + logger.debug("Reconnect requested but it is not allowed {}", () -> getLinkLog(link)); + return; + } + cancelStartupTask(); + closeAndTerminateLink(link); + closeAndTerminateLink(this.link); + setLink(null); + cancelTasks(); + serverResource.disconnected(); + logger.info("Lost connection to host: {}. Attempting reconnection while we still have {} commands in progress.", shell.getConnectedHost(), commandsInProgress.get()); + stopAndCleanupConnection(true); + String host = preferredMSHost; + if (org.apache.commons.lang3.StringUtils.isBlank(host)) { + host = shell.getNextHost(); + } + List avoidMSHostList = shell.getAvoidHosts(); + do { + if (CollectionUtils.isEmpty(avoidMSHostList) || !avoidMSHostList.contains(host)) { + connection = new NioClient(getAgentName(), host, shell.getPort(), shell.getWorkers(), shell.getSslHandshakeTimeout(), this); + logger.info("Reconnecting to host: {}", host); + try { + connection.start(); + } catch (final NioConnectionException e) { + logger.info("Attempted to re-connect to the server, but received an unexpected exception, trying again...", e); + stopAndCleanupConnection(false); + } + } + shell.getBackoffAlgorithm().waitBeforeRetry(); + host = shell.getNextHost(); + } while (!connection.isStartup()); + shell.updateConnectedHost(((NioClient)connection).getHost()); + logger.info("Connected to the host: {}", shell.getConnectedHost()); + } + protected void closeAndTerminateLink(final Link link) { if (link == null) { return; @@ -633,35 +689,6 @@ protected void stopAndCleanupConnection(boolean waitForStop) { } while (connection.isStartup()); } - protected void reconnect(final Link link) { - if (!reconnectAllowed) { - logger.debug("Reconnect requested but it is not allowed {}", () -> getLinkLog(link)); - return; - } - cancelStartupTask(); - closeAndTerminateLink(link); - closeAndTerminateLink(this.link); - setLink(null); - cancelTasks(); - serverResource.disconnected(); - logger.info("Lost connection to host: {}. Attempting reconnection while we still have {} commands in progress.", shell.getConnectedHost(), commandsInProgress.get()); - stopAndCleanupConnection(true); - do { - final String host = shell.getNextHost(); - connection = new NioClient(getAgentName(), host, shell.getPort(), shell.getWorkers(), shell.getSslHandshakeTimeout(), this); - logger.info("Reconnecting to host: {}", host); - try { - connection.start(); - } catch (final NioConnectionException e) { - logger.info("Attempted to re-connect to the server, but received an unexpected exception, trying again...", e); - stopAndCleanupConnection(false); - } - shell.getBackoffAlgorithm().waitBeforeRetry(); - } while (!connection.isStartup()); - shell.updateConnectedHost(); - logger.info("Connected to the host: {}", shell.getConnectedHost()); - } - public void processStartupAnswer(final Answer answer, final Response response, final Link link) { boolean answerValid = cancelStartupTask(); final StartupAnswer startup = (StartupAnswer)answer; @@ -708,6 +735,9 @@ protected void processRequest(final Request request, final Link link) { final Command cmd = cmds[i]; Answer answer; try { + if (cmd.getContextParam("logid") != null) { + ThreadContext.put("logcontextid", cmd.getContextParam("logid")); + } if (logger.isDebugEnabled()) { if (!requestLogged) // ensures request is logged only once per method call { @@ -769,12 +799,17 @@ protected void processRequest(final Request request, final Link link) { } } else if (cmd instanceof SetupMSListCommand) { answer = setupManagementServerList((SetupMSListCommand) cmd); + } else if (cmd instanceof MigrateAgentConnectionCommand) { + answer = migrateAgentToOtherMS((MigrateAgentConnectionCommand) cmd); } else { if (cmd instanceof ReadyCommand) { processReadyCommand(cmd); } commandsInProgress.incrementAndGet(); try { + if (cmd.isReconcile()) { + cmd.setRequestSequence(request.getSequence()); + } answer = serverResource.executeRequest(cmd); } finally { commandsInProgress.decrementAndGet(); @@ -900,7 +935,7 @@ private Answer setupAgentCertificate(final SetupCertificateCommand cmd) { return new SetupCertificateAnswer(true); } - private void processManagementServerList(final List msList, final String lbAlgorithm, final Long lbCheckInterval) { + private void processManagementServerList(final List msList, final List avoidMsList, final String lbAlgorithm, final Long lbCheckInterval, final boolean triggerHostLB) { if (CollectionUtils.isNotEmpty(msList) && StringUtils.isNotEmpty(lbAlgorithm)) { try { final String newMSHosts = String.format("%s%s%s", com.cloud.utils.StringUtils.toCSVList(msList), IAgentShell.hostLbAlgorithmSeparator, lbAlgorithm); @@ -912,18 +947,73 @@ private void processManagementServerList(final List msList, final String throw new CloudRuntimeException("Could not persist received management servers list", e); } } - if ("shuffle".equals(lbAlgorithm)) { - scheduleHostLBCheckerTask(0); - } else { - scheduleHostLBCheckerTask(shell.getLbCheckerInterval(lbCheckInterval)); + shell.setAvoidHosts(avoidMsList); + if (triggerHostLB) { + logger.info("Triggering the preferred host checker task now"); + ScheduledExecutorService hostLbExecutor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("HostLB-Executor")); + hostLbExecutor.schedule(new PreferredHostCheckerTask(), 0, TimeUnit.MILLISECONDS); + hostLbExecutor.shutdown(); } + scheduleHostLBCheckerTask(lbAlgorithm, shell.getLbCheckerInterval(lbCheckInterval)); } private Answer setupManagementServerList(final SetupMSListCommand cmd) { - processManagementServerList(cmd.getMsList(), cmd.getLbAlgorithm(), cmd.getLbCheckInterval()); + processManagementServerList(cmd.getMsList(), cmd.getAvoidMsList(), cmd.getLbAlgorithm(), cmd.getLbCheckInterval(), cmd.getTriggerHostLb()); return new SetupMSListAnswer(true); } + private Answer migrateAgentToOtherMS(final MigrateAgentConnectionCommand cmd) { + try { + if (CollectionUtils.isNotEmpty(cmd.getMsList())) { + processManagementServerList(cmd.getMsList(), cmd.getAvoidMsList(), cmd.getLbAlgorithm(), cmd.getLbCheckInterval(), false); + } + ScheduledExecutorService migrateAgentConnectionService = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory("MigrateAgentConnection-Job")); + migrateAgentConnectionService.schedule(() -> { + migrateAgentConnection(cmd.getAvoidMsList()); + }, 3, TimeUnit.SECONDS); + migrateAgentConnectionService.shutdown(); + } catch (Exception e) { + String errMsg = "Migrate agent connection failed, due to " + e.getMessage(); + logger.debug(errMsg, e); + return new MigrateAgentConnectionAnswer(errMsg); + } + return new MigrateAgentConnectionAnswer(true); + } + + private void migrateAgentConnection(List avoidMsList) { + final String[] msHosts = shell.getHosts(); + if (msHosts == null || msHosts.length < 1) { + throw new CloudRuntimeException("Management Server hosts empty, not properly configured in agent"); + } + + List msHostsList = new ArrayList<>(Arrays.asList(msHosts)); + msHostsList.removeAll(avoidMsList); + if (msHostsList.isEmpty() || StringUtils.isEmpty(msHostsList.get(0))) { + throw new CloudRuntimeException("No other Management Server hosts to migrate"); + } + + String preferredMSHost = null; + for (String msHost : msHostsList) { + try (final Socket socket = new Socket()) { + socket.connect(new InetSocketAddress(msHost, shell.getPort()), 5000); + preferredMSHost = msHost; + break; + } catch (final IOException e) { + throw new CloudRuntimeException("Management server host: " + msHost + " is not reachable, to migrate connection"); + } + } + + if (preferredMSHost == null) { + throw new CloudRuntimeException("Management server host(s) are not reachable, to migrate connection"); + } + + logger.debug("Management server host " + preferredMSHost + " is found to be reachable, trying to reconnect"); + shell.resetHostCounter(); + shell.setAvoidHosts(avoidMsList); + shell.setConnectionTransfer(true); + reconnect(link, preferredMSHost, true); + } + public void processResponse(final Response response, final Link link) { final Answer answer = response.getAnswer(); logger.debug("Received response: {}", response.toString()); @@ -934,12 +1024,21 @@ public void processResponse(final Response response, final Link link) { for (final IAgentControlListener listener : controlListeners) { listener.processControlResponse(response, (AgentControlAnswer)answer); } - } else if (answer instanceof PingAnswer && (((PingAnswer) answer).isSendStartup()) && reconnectAllowed) { + } else if (answer instanceof PingAnswer) { + processPingAnswer((PingAnswer) answer); + } else { + updateLastPingResponseTime(); + } + } + + private void processPingAnswer(final PingAnswer answer) { + if ((answer.isSendStartup()) && reconnectAllowed) { logger.info("Management server requested startup command to reinitialize the agent"); sendStartup(link); } else { - updateLastPingResponseTime(); + serverResource.processPingAnswer((PingAnswer) answer); } + shell.setAvoidHosts(answer.getAvoidMsList()); } public void processReadyCommand(final Command cmd) { @@ -958,7 +1057,7 @@ public void processReadyCommand(final Command cmd) { } verifyAgentArch(ready.getArch()); - processManagementServerList(ready.getMsHostList(), ready.getLbAlgorithm(), ready.getLbCheckInterval()); + processManagementServerList(ready.getMsHostList(), ready.getAvoidMsHostList(), ready.getLbAlgorithm(), ready.getLbCheckInterval(), false); logger.info("Ready command is processed for agent [id: {}, uuid: {}, name: {}]", getId(), getUuid(), getName()); } @@ -1004,6 +1103,9 @@ public void processOtherTask(final Task task) { Answer answer = null; commandsInProgress.incrementAndGet(); try { + if (command.isReconcile()) { + command.setRequestSequence(req.getSequence()); + } answer = serverResource.executeRequest(command); } finally { commandsInProgress.decrementAndGet(); @@ -1127,12 +1229,12 @@ public void run() { public class WatchTask implements Runnable { protected Request _request; protected Agent _agent; - protected Link _link; + protected Link link; public WatchTask(final Link link, final Request request, final Agent agent) { super(); _request = request; - _link = link; + this.link = link; _agent = agent; } @@ -1141,9 +1243,9 @@ public void run() { logger.trace("Scheduling {}", (_request instanceof Response ? "Ping" : "Watch Task")); try { if (_request instanceof Response) { - outRequestHandler.submit(new ServerHandler(Task.Type.OTHER, _link, _request)); + outRequestHandler.submit(new ServerHandler(Task.Type.OTHER, link, _request)); } else { - _link.schedule(new ServerHandler(Task.Type.OTHER, _link, _request)); + link.schedule(new ServerHandler(Task.Type.OTHER, link, _request)); } } catch (final ClosedChannelException e) { logger.warn("Unable to schedule task because channel is closed"); @@ -1152,12 +1254,12 @@ public void run() { } public class StartupTask implements Runnable { - protected Link _link; + protected Link link; private final AtomicBoolean cancelled = new AtomicBoolean(false); public StartupTask(final Link link) { logger.debug("Startup task created"); - _link = link; + this.link = link; } public boolean cancel() { @@ -1175,8 +1277,8 @@ public void run() { logger.info("The running startup command is now invalid. Attempting reconnect"); startupTask.set(null); startupWait = DEFAULT_STARTUP_WAIT * 2; - logger.debug("Executing reconnect from task - {}", () -> getLinkLog(_link)); - reconnect(_link); + logger.debug("Executing reconnect from task - {}", () -> getLinkLog(link)); + reconnect(link); } } } @@ -1209,7 +1311,8 @@ public void doTask(final Task task) throws TaskExecutionException { if (task.getType() == Task.Type.CONNECT) { shell.getBackoffAlgorithm().reset(); setLink(task.getLink()); - sendStartup(task.getLink()); + sendStartup(task.getLink(), shell.isConnectionTransfer()); + shell.setConnectionTransfer(false); } else if (task.getType() == Task.Type.DATA) { Request request; try { @@ -1219,7 +1322,6 @@ public void doTask(final Task task) throws TaskExecutionException { processResponse((Response)request, task.getLink()); } else { //put the requests from mgt server into another thread pool, as the request may take a longer time to finish. Don't block the NIO main thread pool - //processRequest(request, task.getLink()); requestHandler.submit(new AgentRequestHandler(getType(), getLink(), request)); } } catch (final ClassNotFoundException e) { @@ -1235,6 +1337,7 @@ public void doTask(final Task task) throws TaskExecutionException { Thread.sleep(5000); } catch (InterruptedException e) { } + shell.setConnectionTransfer(false); logger.debug("Executing disconnect task - {} and reconnecting", () -> getLinkLog(task.getLink())); reconnect(task.getLink()); } else if (task.getType() == Task.Type.OTHER) { @@ -1304,26 +1407,26 @@ protected void runInContext() { if (msList == null || msList.length < 1) { return; } - final String preferredHost = msList[0]; + final String preferredMSHost = msList[0]; final String connectedHost = shell.getConnectedHost(); logger.debug("Running preferred host checker task, connected host={}, preferred host={}", - connectedHost, preferredHost); - if (preferredHost == null || preferredHost.equals(connectedHost) || link == null) { + connectedHost, preferredMSHost); + if (preferredMSHost == null || preferredMSHost.equals(connectedHost) || link == null) { return; } boolean isHostUp = false; try (final Socket socket = new Socket()) { - socket.connect(new InetSocketAddress(preferredHost, shell.getPort()), 5000); + socket.connect(new InetSocketAddress(preferredMSHost, shell.getPort()), 5000); isHostUp = true; } catch (final IOException e) { - logger.debug("Host: {} is not reachable", preferredHost); + logger.debug("Host: {} is not reachable", preferredMSHost); } if (isHostUp && link != null && commandsInProgress.get() == 0) { if (logger.isDebugEnabled()) { - logger.debug("Preferred host {} is found to be reachable, trying to reconnect", preferredHost); + logger.debug("Preferred host {} is found to be reachable, trying to reconnect", preferredMSHost); } shell.resetHostCounter(); - reconnect(link); + reconnect(link, preferredMSHost, false); } } catch (Throwable t) { logger.error("Error caught while attempting to connect to preferred host", t); diff --git a/agent/src/main/java/com/cloud/agent/AgentShell.java b/agent/src/main/java/com/cloud/agent/AgentShell.java index c5257b95b7c2..4862e7e001e3 100644 --- a/agent/src/main/java/com/cloud/agent/AgentShell.java +++ b/agent/src/main/java/com/cloud/agent/AgentShell.java @@ -66,6 +66,7 @@ public class AgentShell implements IAgentShell, Daemon { private String _zone; private String _pod; private String _host; + private List _avoidHosts; private String _privateIp; private int _port; private int _proxyPort; @@ -76,9 +77,9 @@ public class AgentShell implements IAgentShell, Daemon { private volatile boolean _exit = false; private int _pingRetries; private final List _agents = new ArrayList(); - private String hostToConnect; private String connectedHost; private Long preferredHostCheckInterval; + private boolean connectionTransfer = false; protected AgentProperties agentProperties = new AgentProperties(); public AgentShell() { @@ -120,7 +121,7 @@ public String getNextHost() { if (_hostCounter >= hosts.length) { _hostCounter = 0; } - hostToConnect = hosts[_hostCounter % hosts.length]; + String hostToConnect = hosts[_hostCounter % hosts.length]; _hostCounter++; return hostToConnect; } @@ -142,11 +143,10 @@ public long getLbCheckerInterval(final Long receivedLbInterval) { } @Override - public void updateConnectedHost() { - connectedHost = hostToConnect; + public void updateConnectedHost(String connectedHost) { + this.connectedHost = connectedHost; } - @Override public void resetHostCounter() { _hostCounter = 0; @@ -165,6 +165,16 @@ public void setHosts(final String host) { } } + @Override + public void setAvoidHosts(List avoidHosts) { + _avoidHosts = avoidHosts; + } + + @Override + public List getAvoidHosts() { + return _avoidHosts; + } + @Override public String getPrivateIp() { return _privateIp; @@ -217,6 +227,14 @@ public void setPersistentProperty(String prefix, String name, String value) { _storage.persist(name, value); } + public boolean isConnectionTransfer() { + return connectionTransfer; + } + + public void setConnectionTransfer(boolean connectionTransfer) { + this.connectionTransfer = connectionTransfer; + } + void loadProperties() throws ConfigurationException { final File file = PropertiesUtil.findConfigFile("agent.properties"); diff --git a/agent/src/main/java/com/cloud/agent/IAgentShell.java b/agent/src/main/java/com/cloud/agent/IAgentShell.java index 7f04048795d7..9eefa6d2eeee 100644 --- a/agent/src/main/java/com/cloud/agent/IAgentShell.java +++ b/agent/src/main/java/com/cloud/agent/IAgentShell.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.agent; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -63,13 +64,21 @@ public interface IAgentShell { String[] getHosts(); + void setAvoidHosts(List hosts); + + List getAvoidHosts(); + long getLbCheckerInterval(Long receivedLbInterval); - void updateConnectedHost(); + void updateConnectedHost(String connectedHost); String getConnectedHost(); void launchNewAgent(ServerResource resource) throws ConfigurationException; + boolean isConnectionTransfer(); + + void setConnectionTransfer(boolean connectionTransfer); + Integer getSslHandshakeTimeout(); } diff --git a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java index c781c07c227f..e4775188d0ca 100644 --- a/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java +++ b/agent/src/main/java/com/cloud/agent/properties/AgentProperties.java @@ -117,12 +117,26 @@ public class AgentProperties{ /** * Local storage path.
- * This property allows multiple values to be entered in a single String. The differente values must be separated by commas.
+ * This property allows multiple values to be entered in a single String. The different values must be separated by commas.
* Data type: String.
* Default value: /var/lib/libvirt/images/ */ public static final Property LOCAL_STORAGE_PATH = new Property<>("local.storage.path", "/var/lib/libvirt/images/"); + /** + * Enables TLS on the KVM image server transfer endpoint.
+ * Data type: Boolean.
+ * Default value: true + */ + public static final Property IMAGE_SERVER_TLS_ENABLED = new Property<>("image.server.tls.enabled", true); + + /** + * The IP address that the KVM image server listens on.
+ * Data type: String.
+ * Default value: null + */ + public static final Property IMAGE_SERVER_LISTEN_ADDRESS = new Property<>("image.server.listen.address", null, String.class); + /** * Directory where Qemu sockets are placed.
* These sockets are for the Qemu Guest Agent and SSVM provisioning.
@@ -134,7 +148,7 @@ public class AgentProperties{ /** * MANDATORY: The UUID for the local storage pool.
- * This property allows multiple values to be entered in a single String. The differente values must be separated by commas.
+ * This property allows multiple values to be entered in a single String. The different values must be separated by commas.
* Data type: String.
* Default value: null */ @@ -155,6 +169,15 @@ public class AgentProperties{ */ public static final Property CMDS_TIMEOUT = new Property<>("cmds.timeout", 7200); + /** + * The timeout (in seconds) for QCOW2 delta merge operations, mainly used for classic volume snapshots, disk-only VM snapshots on file-based storage, and the KBOSS plugin. + * If a value of 0 or less is informed, the default will be used.
+ * This configuration is only considered if libvirt.events.enabled is also true.
+ * Data type: Integer.
+ * Default value: 259200 + */ + public static final Property QCOW2_DELTA_MERGE_TIMEOUT = new Property<>("qcow2.delta.merge.timeout", 60 * 60 * 72); + /** * This parameter sets the VM migration speed (in mbps). The default value is -1,
* which means that the agent will try to guess the speed of the guest network and consume all possible bandwidth.
@@ -213,6 +236,15 @@ public class AgentProperties{ */ public static final Property AGENT_HOOKS_LIBVIRT_VM_XML_TRANSFORMER_SCRIPT = new Property<>("agent.hooks.libvirt_vm_xml_transformer.script", "libvirt-vm-xml-transformer.groovy"); + /** + * This property is used with the agent.hooks.basedir property to define the Libvirt VM XML transformer shell script.
+ * The shell script is used to execute the Libvirt VM XML transformer script.
+ * For more information see the agent.properties file.
+ * Data type: String.
+ * Default value: libvirt-vm-xml-transformer.sh + */ + public static final Property AGENT_HOOKS_LIBVIRT_VM_XML_TRANSFORMER_SHELL_SCRIPT = new Property<>("agent.hooks.libvirt_vm_xml_transformer.shell_script", "libvirt-vm-xml-transformer.sh"); + /** * This property is used with the agent.hooks.basedir and agent.hooks.libvirt_vm_xml_transformer.script properties to define the Libvirt VM XML transformer method.
* Libvirt XML transformer hook does XML-to-XML transformation.
@@ -233,6 +265,15 @@ public class AgentProperties{ */ public static final Property AGENT_HOOKS_LIBVIRT_VM_ON_START_SCRIPT = new Property<>("agent.hooks.libvirt_vm_on_start.script", "libvirt-vm-state-change.groovy"); + /** + * This property is used with the agent.hooks.basedir property to define the Libvirt VM on start shell script.
+ * The shell script is used to execute the Libvirt VM on start script.
+ * For more information see the agent.properties file.
+ * Data type: String.
+ * Default value: libvirt-vm-state-change.sh + */ + public static final Property AGENT_HOOKS_LIBVIRT_VM_ON_START_SHELL_SCRIPT = new Property<>("agent.hooks.libvirt_vm_on_start.shell_script", "libvirt-vm-state-change.sh"); + /** * This property is used with the agent.hooks.basedir and agent.hooks.libvirt_vm_on_start.script properties to define the Libvirt VM on start method.
* The hook is called right after Libvirt successfully launched the VM.
@@ -252,6 +293,15 @@ public class AgentProperties{ */ public static final Property AGENT_HOOKS_LIBVIRT_VM_ON_STOP_SCRIPT = new Property<>("agent.hooks.libvirt_vm_on_stop.script", "libvirt-vm-state-change.groovy"); + /** + * This property is used with the agent.hooks.basedir property to define the Libvirt VM on stop shell script.
+ * The shell script is used to execute the Libvirt VM on stop script.
+ * For more information see the agent.properties file.
+ * Data type: String.
+ * Default value: libvirt-vm-state-change.sh + */ + public static final Property AGENT_HOOKS_LIBVIRT_VM_ON_STOP_SHELL_SCRIPT = new Property<>("agent.hooks.libvirt_vm_on_stop.shell_script", "libvirt-vm-state-change.sh"); + /** * This property is used with the agent.hooks.basedir and agent.hooks.libvirt_vm_on_stop.script properties to define the Libvirt VM on stop method.
* The hook is called right after libvirt successfully stopped the VM.
@@ -269,6 +319,15 @@ public class AgentProperties{ */ public static final Property NETWORK_BRIDGE_TYPE = new Property<>("network.bridge.type", "native"); + /** + * Sets the VXLAN networking mode used by the BridgeVifDriver.
+ * Possible values: multicast | evpn
+ * When set to evpn, the driver will use modifyvxlan-evpn.sh instead of modifyvxlan.sh.
+ * Data type: String.
+ * Default value: multicast + */ + public static final Property NETWORK_VXLAN_MODE = new Property<>("network.vxlan.mode", "multicast"); + /** * Sets the driver used to plug and unplug NICs from the bridges.
* A sensible default value will be selected based on the network.bridge.type but can be overridden here.
@@ -383,15 +442,16 @@ public class AgentProperties{ /** * This param will set the CPU architecture for the domain to override what the management server would send.
* In case of arm64 (aarch64), this will change the machine type to 'virt' and add a SCSI and a USB controller in the domain XML.
- * Possible values: x86_64 | aarch64
+ * Possible values: x86_64 | aarch64 | s390x
* Data type: String.
* Default value: null (will set use the architecture of the VM's OS). */ public static final Property GUEST_CPU_ARCH = new Property<>("guest.cpu.arch", null, String.class); /** - * This param will require CPU features on the CPU section.
- * The features listed in this property must be separated by a blank space (see example below).
+ * Specifies required CPU features for end-user and system VMs.
+ * These features must be present on the host CPU for VM deployment.
+ * Multiple features should be separated by whitespace (see example below).
* Possible values: vmx vme
* Data type: String.
* Default value: null @@ -557,10 +617,10 @@ public class AgentProperties{ /** * This parameter specifies if the host must be rebooted when something goes wrong with the heartbeat.
* Data type: Boolean.
- * Default value: true + * Default value: false */ public static final Property REBOOT_HOST_AND_ALERT_MANAGEMENT_ON_HEARTBEAT_TIMEOUT - = new Property<>("reboot.host.and.alert.management.on.heartbeat.timeout", true); + = new Property<>("reboot.host.and.alert.management.on.heartbeat.timeout", false); /** * Enables manually setting CPU's topology on KVM's VM.
@@ -758,6 +818,44 @@ public Property getWorkers() { */ public static final Property VIRTV2V_VERBOSE_ENABLED = new Property<>("virtv2v.verbose.enabled", false); + /** + * Set env TMPDIR var for virt-v2v Instance Conversion from VMware to KVM + * Data type: String.
+ * Default value: null + */ + public static final Property CONVERT_ENV_TMPDIR = new Property<>("convert.instance.env.tmpdir", null, String.class); + + /** + * Set env VIRT_V2V_TMPDIR var for virt-v2v Instance Conversion from VMware to KVM + * Data type: String.
+ * Default value: null + */ + public static final Property CONVERT_ENV_VIRTV2V_TMPDIR = new Property<>("convert.instance.env.virtv2v.tmpdir", null, String.class); + + /** + * Path to the VDDK library directory on the KVM conversion host, used when converting VMs from VMware to KVM via VDDK. + * This directory is passed to virt-v2v as -io vddk-libdir=<path>. + * Data type: String.
+ * Default value: null + */ + public static final Property VDDK_LIB_DIR = new Property<>("vddk.lib.dir", null, String.class); + + /** + * Ordered list of VDDK transports for virt-v2v, passed as -io vddk-transports=<value>. + * Example: nbd:nbdssl. + * Data type: String.
+ * Default value: null + */ + public static final Property VDDK_TRANSPORTS = new Property<>("vddk.transports", null, String.class); + + /** + * vCenter TLS certificate thumbprint used by virt-v2v VDDK mode, passed as -io vddk-thumbprint=<value>. + * If unset, the KVM host computes it at runtime from the vCenter endpoint. + * Data type: String.
+ * Default value: null + */ + public static final Property VDDK_THUMBPRINT = new Property<>("vddk.thumbprint", null, String.class); + /** * BGP controll CIDR * Data type: String.
@@ -816,7 +914,57 @@ public Property getWorkers() { * Data type: Integer.
* Default value: null */ - public static final Property SSL_HANDSHAKE_TIMEOUT = new Property<>("ssl.handshake.timeout", null, Integer.class); + public static final Property SSL_HANDSHAKE_TIMEOUT = new Property<>("ssl.handshake.timeout", 30, Integer.class); + + /** + * Timeout (in seconds) to wait for the incremental snapshot to complete. + * */ + public static final Property INCREMENTAL_SNAPSHOT_TIMEOUT = new Property<>("incremental.snapshot.timeout", 10800); + + /** + * Timeout (in seconds) to wait for the snapshot reversion to complete. + * */ + public static final Property REVERT_SNAPSHOT_TIMEOUT = new Property<>("revert.snapshot.timeout", 10800); + + /** + * If set to true, creates VMs as full clones of their templates on KVM hypervisor. Creates as linked clones otherwise.
+ * Data type: Boolean.
+ * Default value: false + */ + public static final Property CREATE_FULL_CLONE = new Property<>("create.full.clone", false); + + /** + * Time, in seconds, to wait before retrying to rebase during the incremental snapshot process. + * */ + public static final Property INCREMENTAL_SNAPSHOT_RETRY_REBASE_WAIT = new Property<>("incremental.snapshot.retry.rebase.wait", 60); + + /** + * When set to true, executes modifymacip.sh (resolved via the + * network scripts directory) on VM NIC plug (VM start) and unplug (VM stop) to manage static + * ARP/NDP entries and host routes for VM interfaces.
+ * The script is invoked with:
+ *   add: -o add -b <bridge> -m <mac> [-4 <ipv4>] [-6 <ipv6>]
+ *   delete: -o delete -b <bridge> -m <mac>
+ * A bundled reference implementation is available at + * scripts/vm/network/vnet/modifymacip.sh.
+ * Set to false or leave unset to disable this feature.
+ * Data type: Boolean.
+ * Default value: false + */ + public static final Property VM_NETWORK_MACIP_STATIC = new Property<>("vm.network.macip.static", false, Boolean.class); + + + /** + * Maximum number of backup validation jobs that can be executed at the same time. Values lower than 0 remove the limit, meaning that as many validations as possible will be done at + * the same time. + */ + public static final Property BACKUP_VALIDATION_MAX_CONCURRENT_OPERATIONS_PER_HOST = new Property<>("backup.validation.max.concurrent.operations.per.host", null, Integer.class); + + /** + * Maximum number of backup compression jobs that can be executed at the same time. Values lower than 0 remove the limit, meaning that as many compressions as possible will be + * done at the same time. + */ + public static final Property BACKUP_COMPRESSION_MAX_CONCURRENT_OPERATIONS_PER_HOST = new Property<>("backup.compression.max.concurrent.operations.per.host", null, Integer.class); public static class Property { private String name; diff --git a/agent/src/main/java/com/cloud/agent/resource/DummyResource.java b/agent/src/main/java/com/cloud/agent/resource/DummyResource.java index fe519ca9497f..4002e53b5858 100644 --- a/agent/src/main/java/com/cloud/agent/resource/DummyResource.java +++ b/agent/src/main/java/com/cloud/agent/resource/DummyResource.java @@ -20,7 +20,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.UUID; import com.cloud.agent.IAgentControl; import com.cloud.agent.api.Answer; @@ -40,6 +39,7 @@ import com.cloud.storage.Storage; import com.cloud.storage.Storage.StoragePoolType; import com.cloud.utils.StringUtils; +import com.cloud.utils.UuidUtils; public class DummyResource implements ServerResource { String _name; @@ -133,7 +133,7 @@ protected StoragePoolInfo initializeLocalStorage() { String hostIp = getConfiguredProperty("private.ip.address", "127.0.0.1"); String localStoragePath = getConfiguredProperty("local.storage.path", "/mnt"); String lh = hostIp + localStoragePath; - String uuid = UUID.nameUUIDFromBytes(lh.getBytes(StringUtils.getPreferredCharset())).toString(); + String uuid = UuidUtils.nameUUIDFromBytes(lh.getBytes(StringUtils.getPreferredCharset())).toString(); String capacity = getConfiguredProperty("local.storage.capacity", "1000000000"); String available = getConfiguredProperty("local.storage.avail", "10000000"); diff --git a/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java b/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java index 83b11418f2c5..ef98fa532ecb 100644 --- a/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java +++ b/agent/src/main/java/com/cloud/agent/resource/consoleproxy/ConsoleProxyResource.java @@ -397,9 +397,8 @@ protected void runInContext() { } public String authenticateConsoleAccess(String host, String port, String vmId, String sid, String ticket, - Boolean isReauthentication, String sessionToken) { - - ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(host, port, vmId, sid, ticket, sessionToken); + Boolean isReauthentication, String sessionToken, String clientAddress) { + ConsoleAccessAuthenticationCommand cmd = new ConsoleAccessAuthenticationCommand(host, port, vmId, sid, ticket, sessionToken, clientAddress); cmd.setReauthenticating(isReauthentication); ConsoleProxyAuthenticationResult result = new ConsoleProxyAuthenticationResult(); diff --git a/agent/src/test/java/com/cloud/agent/AgentShellTest.java b/agent/src/test/java/com/cloud/agent/AgentShellTest.java index 6d9758cc3dc8..d8def24a603a 100644 --- a/agent/src/test/java/com/cloud/agent/AgentShellTest.java +++ b/agent/src/test/java/com/cloud/agent/AgentShellTest.java @@ -358,7 +358,7 @@ public void updateAndGetConnectedHost() { AgentShell shell = new AgentShell(); shell.setHosts("test"); shell.getNextHost(); - shell.updateConnectedHost(); + shell.updateConnectedHost("test"); Assert.assertEquals(expected, shell.getConnectedHost()); } diff --git a/api/pom.xml b/api/pom.xml index caa33b662180..4cdb57b6414c 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT @@ -71,6 +71,11 @@ cloud-framework-direct-download ${project.version} + + org.apache.cloudstack + cloud-framework-kms + ${project.version} + diff --git a/api/src/main/java/com/cloud/agent/api/Command.java b/api/src/main/java/com/cloud/agent/api/Command.java index eb979c0060b9..c4e99cb41707 100644 --- a/api/src/main/java/com/cloud/agent/api/Command.java +++ b/api/src/main/java/com/cloud/agent/api/Command.java @@ -19,9 +19,10 @@ import java.util.HashMap; import java.util.Map; -import com.cloud.agent.api.LogLevel.Log4jLevel; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import com.cloud.agent.api.LogLevel.Log4jLevel; /** * implemented by classes that extends the Command class. Command specifies @@ -35,6 +36,23 @@ public static enum OnError { Continue, Stop } + public enum State { + CREATED, // Command is created by management server + STARTED, // Command is started by agent + PROCESSING, // Processing by agent + PROCESSING_IN_BACKEND, // Processing in backend by agent + COMPLETED, // Operation succeeds by agent or management server + FAILED, // Operation fails by agent + RECONCILE_RETRY, // Ready for retry of reconciliation + RECONCILING, // Being reconciled by management server + RECONCILED, // Reconciled by management server + RECONCILE_SKIPPED, // Skip the reconciliation as the resource state is inconsistent with the command + RECONCILE_FAILED, // Fail to reconcile by management server + TIMED_OUT, // Timed out on management server or agent + INTERRUPTED, // Interrupted by management server or agent (for example agent is restarted), + DANGLED_IN_BACKEND // Backend process which cannot be processed normally (for example agent is restarted) + } + public static final String HYPERVISOR_TYPE = "hypervisorType"; // allow command to carry over hypervisor or other environment related context info @@ -42,6 +60,8 @@ public static enum OnError { protected Map contextMap = new HashMap(); private int wait; //in second private boolean bypassHostMaintenance = false; + private transient long requestSequence = 0L; + protected Map> externalDetails; protected Command() { this.wait = 0; @@ -82,6 +102,10 @@ public String getContextParam(String name) { return contextMap.get(name); } + public Map getContextMap() { + return contextMap; + } + public boolean allowCaching() { return true; } @@ -94,6 +118,26 @@ public void setBypassHostMaintenance(boolean bypassHostMaintenance) { this.bypassHostMaintenance = bypassHostMaintenance; } + public boolean isReconcile() { + return false; + } + + public long getRequestSequence() { + return requestSequence; + } + + public void setRequestSequence(long requestSequence) { + this.requestSequence = requestSequence; + } + + public void setExternalDetails(Map> externalDetails) { + this.externalDetails = externalDetails; + } + + public Map> getExternalDetails() { + return externalDetails; + } + @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java b/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java index 85ffc1898209..5515a9c48bcb 100644 --- a/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java +++ b/api/src/main/java/com/cloud/agent/api/VgpuTypesInfo.java @@ -15,10 +15,24 @@ // specific language governing permissions and limitations // under the License. package com.cloud.agent.api; + +import org.apache.cloudstack.gpu.GpuDevice; + public class VgpuTypesInfo { + private boolean passthroughEnabled = true; + private GpuDevice.DeviceType deviceType; + private String parentBusAddress; + private String busAddress; + private String numaNode; + private String pciRoot; + private String deviceId; + private String deviceName; + private String vendorId; + private String vendorName; private String modelName; private String groupName; + private String vmName; private Long maxHeads; private Long videoRam; private Long maxResolutionX; @@ -26,6 +40,7 @@ public class VgpuTypesInfo { private Long maxVgpuPerGpu; private Long remainingCapacity; private Long maxCapacity; + private boolean display = false; public String getModelName() { return modelName; @@ -39,22 +54,42 @@ public Long getVideoRam() { return videoRam; } + public void setVideoRam(Long videoRam) { + this.videoRam = videoRam; + } + public Long getMaxHeads() { return maxHeads; } + public void setMaxHeads(Long maxHeads) { + this.maxHeads = maxHeads; + } + public Long getMaxResolutionX() { return maxResolutionX; } + public void setMaxResolutionX(Long maxResolutionX) { + this.maxResolutionX = maxResolutionX; + } + public Long getMaxResolutionY() { return maxResolutionY; } + public void setMaxResolutionY(Long maxResolutionY) { + this.maxResolutionY = maxResolutionY; + } + public Long getMaxVpuPerGpu() { return maxVgpuPerGpu; } + public void setMaxVgpuPerGpu(Long maxVgpuPerGpu) { + this.maxVgpuPerGpu = maxVgpuPerGpu; + } + public Long getRemainingCapacity() { return remainingCapacity; } @@ -71,8 +106,133 @@ public void setMaxVmCapacity(Long maxCapacity) { this.maxCapacity = maxCapacity; } - public VgpuTypesInfo(String groupName, String modelName, Long videoRam, Long maxHeads, Long maxResolutionX, Long maxResolutionY, Long maxVgpuPerGpu, - Long remainingCapacity, Long maxCapacity) { + public boolean isPassthroughEnabled() { + return passthroughEnabled; + } + + public void setPassthroughEnabled(boolean passthroughEnabled) { + this.passthroughEnabled = passthroughEnabled; + } + + public GpuDevice.DeviceType getDeviceType() { + return deviceType; + } + + public void setDeviceType(GpuDevice.DeviceType deviceType) { + this.deviceType = deviceType; + } + + public String getParentBusAddress() { + return parentBusAddress; + } + + public void setParentBusAddress(String parentBusAddress) { + this.parentBusAddress = parentBusAddress; + } + + public String getBusAddress() { + return busAddress; + } + + public void setBusAddress(String busAddress) { + this.busAddress = busAddress; + } + + public String getNumaNode() { + return numaNode; + } + + public void setNumaNode(String numaNode) { + this.numaNode = numaNode; + } + + public String getPciRoot() { + return pciRoot; + } + + public void setPciRoot(String pciRoot) { + this.pciRoot = pciRoot; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getVendorId() { + return vendorId; + } + + public void setVendorId(String vendorId) { + this.vendorId = vendorId; + } + + public String getVendorName() { + return vendorName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public boolean isDisplay() { + return display; + } + + public void setDisplay(boolean display) { + this.display = display; + } + + public VgpuTypesInfo(GpuDevice.DeviceType deviceType, String groupName, String modelName, String busAddress, + String vendorId, String vendorName, String deviceId, String deviceName, String numaNode, String pciRoot + ) { + this.deviceType = deviceType; + this.groupName = groupName; + this.modelName = modelName; + this.busAddress = busAddress; + this.deviceId = deviceId; + this.deviceName = deviceName; + this.vendorId = vendorId; + this.vendorName = vendorName; + this.numaNode = numaNode; + this.pciRoot = pciRoot; + } + + public VgpuTypesInfo(GpuDevice.DeviceType deviceType, String groupName, String modelName, String busAddress, + String vendorId, String vendorName, String deviceId, String deviceName + ) { + this.deviceType = deviceType; + this.groupName = groupName; + this.modelName = modelName; + this.busAddress = busAddress; + this.deviceId = deviceId; + this.deviceName = deviceName; + this.vendorId = vendorId; + this.vendorName = vendorName; + } + + public VgpuTypesInfo(String groupName, String modelName, Long videoRam, Long maxHeads, Long maxResolutionX, + Long maxResolutionY, Long maxVgpuPerGpu, Long remainingCapacity, Long maxCapacity + ) { this.groupName = groupName; this.modelName = modelName; this.videoRam = videoRam; diff --git a/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java b/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java index ccb5362c8537..23167c5c53b0 100644 --- a/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java +++ b/api/src/main/java/com/cloud/agent/api/storage/OVFHelper.java @@ -119,8 +119,7 @@ protected OVFPropertyTO createOVFPropertyFromNode(Node node, int index, String c boolean password = StringUtils.isNotBlank(passStr) && passStr.equalsIgnoreCase("true"); String label = ovfParser.getChildNodeValue(node, "Label"); String description = ovfParser.getChildNodeValue(node, "Description"); - logger.debug("Creating OVF property index " + index + (category == null ? "" : " for category " + category) - + " with key = " + key); + logger.debug("Creating OVF property index {} {} with key = {}", index, (category == null ? "" : " for category " + category), key); return new OVFPropertyTO(key, type, value, qualifiers, userConfigurable, label, description, password, index, category); } @@ -152,7 +151,7 @@ public List getConfigurableOVFPropertiesFromDocument(Document doc if (child.getNodeName().equalsIgnoreCase("Category") || child.getNodeName().endsWith(":Category")) { lastCategoryFound = child.getTextContent(); - logger.info("Category found " + lastCategoryFound); + logger.info("Category found {}", lastCategoryFound); } else if (child.getNodeName().equalsIgnoreCase("Property") || child.getNodeName().endsWith(":Property")) { OVFPropertyTO prop = createOVFPropertyFromNode(child, propertyIndex, lastCategoryFound); @@ -250,13 +249,13 @@ private List matchHardwareItemsToDiskAndFilesInformation(List extractDisksFromOvfDocumentTree(Document doc) { od._controller = getControllerType(items, od._diskId); vd.add(od); } - if (logger.isTraceEnabled()) { - logger.trace(String.format("Found %d disk definitions", vd.size())); - } + logger.trace("Found {} disk definitions", vd.size()); return vd; } @@ -366,9 +363,7 @@ protected List extractFilesFromOvfDocumentTree(File ovfFile, Document d vf.add(of); } } - if (logger.isTraceEnabled()) { - logger.trace(String.format("Found %d file definitions in %s", vf.size(), ovfFile.getPath())); - } + logger.trace("Found {} file definitions in {}", vf.size(), ovfFile.getPath()); return vf; } @@ -506,7 +501,7 @@ private void writeDocumentToFile(String newOvfFilePath, Document doc) { outfile.write(writer.toString()); outfile.close(); } catch (IOException | TransformerException e) { - logger.info("Unexpected exception caught while rewriting OVF:" + e.getMessage(), e); + logger.info("Unexpected exception caught while rewriting OVF: {}", e.getMessage(), e); throw new CloudRuntimeException(e); } } @@ -522,9 +517,7 @@ OVFFile getFileDefinitionFromDiskDefinition(String fileRef, List files) public List getNetPrerequisitesFromDocument(Document doc) throws InternalErrorException { if (doc == null) { - if (logger.isTraceEnabled()) { - logger.trace("No document to parse; returning no prerequisite Networks"); - } + logger.trace("No document to parse; returning no prerequisite networks"); return Collections.emptyList(); } @@ -540,9 +533,7 @@ public List getNetPrerequisitesFromDocument(Document doc) throws I private void matchNicsToNets(Map nets, Node systemElement) { final DocumentTraversal traversal = (DocumentTraversal) systemElement; final NodeIterator iterator = traversal.createNodeIterator(systemElement, NodeFilter.SHOW_ELEMENT, null, true); - if (logger.isTraceEnabled()) { - logger.trace(String.format("Starting out with %d network-prerequisites, parsing hardware",nets.size())); - } + logger.trace("Starting out with {} network-prerequisites, parsing hardware", nets.size()); int nicCount = 0; for (Node n = iterator.nextNode(); n != null; n = iterator.nextNode()) { final Element e = (Element) n; @@ -550,9 +541,7 @@ private void matchNicsToNets(Map nets, Node systemElement) nicCount++; String name = e.getTextContent(); // should be in our nets if(nets.get(name) == null) { - if(logger.isInfoEnabled()) { - logger.info(String.format("Found a NIC definition without a Network definition by name %s, adding it to the list.", name)); - } + logger.info("Found a NIC definition without a Network definition by name {}, adding it to the list.", name); nets.put(name, new OVFNetworkTO()); } OVFNetworkTO thisNet = nets.get(name); @@ -561,9 +550,7 @@ private void matchNicsToNets(Map nets, Node systemElement) } } } - if (logger.isTraceEnabled()) { - logger.trace(String.format("Ending up with %d network-prerequisites, parsed %d NICs", nets.size(), nicCount)); - } + logger.trace("Ending up with {} network-prerequisites, parsed {} nics", nets.size(), nicCount); } /** @@ -585,7 +572,7 @@ private void fillNicPrerequisites(OVFNetworkTO nic, Node parentNode) { int addressOnParent = Integer.parseInt(addressOnParentStr); nic.setAddressOnParent(addressOnParent); } catch (NumberFormatException e) { - logger.warn("Encountered element of type \"AddressOnParent\", that could not be parse to an integer number: " + addressOnParentStr); + logger.warn("Encountered element of type \"AddressOnParent\", that could not be parse to an integer number: {}", addressOnParentStr); } boolean automaticAllocation = StringUtils.isNotBlank(automaticAllocationStr) && Boolean.parseBoolean(automaticAllocationStr); @@ -597,7 +584,7 @@ private void fillNicPrerequisites(OVFNetworkTO nic, Node parentNode) { int instanceId = Integer.parseInt(instanceIdStr); nic.setInstanceID(instanceId); } catch (NumberFormatException e) { - logger.warn("Encountered element of type \"InstanceID\", that could not be parse to an integer number: " + instanceIdStr); + logger.warn("Encountered element of type \"InstanceID\", that could not be parse to an integer number: {}", instanceIdStr); } nic.setResourceSubType(resourceSubType); @@ -630,9 +617,7 @@ private Map getNetworksFromDocumentTree(Document doc) { nets.put(networkName,network); } - if (logger.isTraceEnabled()) { - logger.trace(String.format("Found %d Networks in Template", nets.size())); - } + logger.trace("Found {} Networks in Template", nets.size()); return nets; } @@ -771,7 +756,7 @@ private Long getLongValueFromString(String value) { try { return Long.parseLong(value); } catch (NumberFormatException e) { - logger.debug("Could not parse the value: " + value + ", ignoring it"); + logger.debug("Could not parse the value: {}, ignoring it", value); } } return null; @@ -782,7 +767,7 @@ private Integer getIntValueFromString(String value) { try { return Integer.parseInt(value); } catch (NumberFormatException e) { - logger.debug("Could not parse the value: " + value + ", ignoring it"); + logger.debug("Could not parse the value: {}, ignoring it", value); } } return null; @@ -820,7 +805,7 @@ public List getEulaSectionsFromDocument(Document doc) { try { compressedLicense = compressOVFEula(eulaLicense); } catch (IOException e) { - logger.error("Could not compress the license for info " + eulaInfo); + logger.error("Could not compress the license for info {}", eulaInfo); continue; } OVFEulaSectionTO eula = new OVFEulaSectionTO(eulaInfo, compressedLicense, eulaIndex); diff --git a/api/src/main/java/com/cloud/agent/api/storage/OVFParser.java b/api/src/main/java/com/cloud/agent/api/storage/OVFParser.java index 38f478d63cf8..316ab4ea87b9 100644 --- a/api/src/main/java/com/cloud/agent/api/storage/OVFParser.java +++ b/api/src/main/java/com/cloud/agent/api/storage/OVFParser.java @@ -54,7 +54,7 @@ public OVFParser() { documentBuilderFactory.setNamespaceAware(true); documentBuilder = documentBuilderFactory.newDocumentBuilder(); } catch (ParserConfigurationException e) { - logger.error("Cannot start the OVF parser: " + e.getMessage(), e); + logger.error("Cannot start the OVF parser: {}", e.getMessage(), e); } } @@ -70,7 +70,7 @@ public Document parseOVFFile(String ovfFilePath) { try { return documentBuilder.parse(new File(ovfFilePath)); } catch (SAXException | IOException e) { - logger.error("Error parsing " + ovfFilePath + " " + e.getMessage(), e); + logger.error("Error parsing {} {}", ovfFilePath, e.getMessage(), e); return null; } } diff --git a/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java b/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java index 26294cfbb223..76a75e03ba55 100644 --- a/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java +++ b/api/src/main/java/com/cloud/agent/api/to/DataObjectType.java @@ -19,5 +19,5 @@ package com.cloud.agent.api.to; public enum DataObjectType { - VOLUME, SNAPSHOT, TEMPLATE, ARCHIVE + VOLUME, SNAPSHOT, TEMPLATE, ARCHIVE, BACKUP } diff --git a/api/src/main/java/com/cloud/agent/api/to/DiskTO.java b/api/src/main/java/com/cloud/agent/api/to/DiskTO.java index d22df2df172e..5664de790919 100644 --- a/api/src/main/java/com/cloud/agent/api/to/DiskTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/DiskTO.java @@ -46,7 +46,7 @@ public class DiskTO { private Long diskSeq; private String path; private Volume.Type type; - private Map _details; + private Map details; public DiskTO() { @@ -92,10 +92,10 @@ public void setType(Volume.Type type) { } public void setDetails(Map details) { - _details = details; + this.details = details; } public Map getDetails() { - return _details; + return details; } } diff --git a/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java b/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java index 25c75001a3c1..69350815be3a 100644 --- a/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/FirewallRuleTO.java @@ -47,7 +47,7 @@ public class FirewallRuleTO implements InternalIdentity { int[] srcPortRange; boolean revoked; boolean alreadyAdded; - private List sourceCidrList; + protected List sourceCidrList; private List destCidrList; FirewallRule.Purpose purpose; private Integer icmpType; diff --git a/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java b/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java index 4afe080477b7..6e9cee06dd38 100644 --- a/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/GPUDeviceTO.java @@ -16,7 +16,9 @@ // under the License. package com.cloud.agent.api.to; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import com.cloud.agent.api.VgpuTypesInfo; @@ -24,9 +26,23 @@ public class GPUDeviceTO { private String gpuGroup; private String vgpuType; + private int gpuCount; private HashMap> groupDetails = new HashMap>(); + private List gpuDevices = new ArrayList<>(); - public GPUDeviceTO( String gpuGroup, String vgpuType, HashMap> groupDetails) { + public GPUDeviceTO(String gpuGroup, String vgpuType, int gpuCount, + HashMap> groupDetails, + List gpuDevices) { + this.gpuGroup = gpuGroup; + this.vgpuType = vgpuType; + this.groupDetails = groupDetails; + this.gpuCount = gpuCount; + this.gpuDevices = gpuDevices; + + } + + public GPUDeviceTO(String gpuGroup, String vgpuType, + HashMap> groupDetails) { this.gpuGroup = gpuGroup; this.vgpuType = vgpuType; this.groupDetails = groupDetails; @@ -48,6 +64,14 @@ public void setVgpuType(String vgpuType) { this.vgpuType = vgpuType; } + public int getGpuCount() { + return gpuCount; + } + + public void setGpuCount(int gpuCount) { + this.gpuCount = gpuCount; + } + public HashMap> getGroupDetails() { return groupDetails; } @@ -56,4 +80,11 @@ public void setGroupDetails(HashMap> grou this.groupDetails = groupDetails; } + public List getGpuDevices() { + return gpuDevices; + } + + public void setGpuDevices(List gpuDevices) { + this.gpuDevices = gpuDevices; + } } diff --git a/api/src/main/java/com/cloud/agent/api/to/LoadBalancerTO.java b/api/src/main/java/com/cloud/agent/api/to/LoadBalancerTO.java index f395f26aeed6..6c4b9e607c51 100644 --- a/api/src/main/java/com/cloud/agent/api/to/LoadBalancerTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/LoadBalancerTO.java @@ -71,7 +71,7 @@ public LoadBalancerTO(String uuid, String srcIp, int srcPort, String protocol, S this.destinations = new DestinationTO[destinations.size()]; this.stickinessPolicies = null; this.sslCert = null; - this.lbProtocol = null; + this.lbProtocol = protocol; int i = 0; for (LbDestination destination : destinations) { this.destinations[i++] = new DestinationTO(destination.getIpAddress(), destination.getDestinationPortStart(), destination.isRevoked(), false); @@ -205,6 +205,10 @@ public LbSslCert getSslCert() { return this.sslCert; } + public void setLbSslCert(LbSslCert sslCert) { + this.sslCert = sslCert; + } + public String getSrcIpVlan() { return srcIpVlan; } diff --git a/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java b/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java index bd08ce811013..d65ec0e3daad 100644 --- a/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/NetworkTO.java @@ -36,7 +36,7 @@ public class NetworkTO { protected TrafficType type; protected URI broadcastUri; protected URI isolationUri; - protected boolean isSecurityGroupEnabled; + protected boolean securityGroupEnabled; protected String name; protected String ip6address; protected String ip6gateway; @@ -112,7 +112,7 @@ public String getName() { } public void setSecurityGroupEnabled(boolean enabled) { - this.isSecurityGroupEnabled = enabled; + this.securityGroupEnabled = enabled; } /** @@ -221,7 +221,7 @@ public void setIsolationuri(URI isolationUri) { } public boolean isSecurityGroupEnabled() { - return this.isSecurityGroupEnabled; + return this.securityGroupEnabled; } public void setIp6Dns1(String ip6Dns1) { diff --git a/api/src/main/java/com/cloud/agent/api/to/NicTO.java b/api/src/main/java/com/cloud/agent/api/to/NicTO.java index 573363c04fb1..2ed7d9f9a201 100644 --- a/api/src/main/java/com/cloud/agent/api/to/NicTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/NicTO.java @@ -33,6 +33,7 @@ public class NicTO extends NetworkTO { boolean dpdkEnabled; Integer mtu; Long networkId; + boolean enabled; String networkSegmentName; @@ -86,6 +87,14 @@ public void setUuid(String uuid) { this.nicUuid = uuid; } + public String getNicUuid() { + return nicUuid; + } + + public void setNicUuid(String nicUuid) { + this.nicUuid = nicUuid; + } + @Override public String toString() { return new StringBuilder("[Nic:").append(type).append("-").append(ip).append("-").append(broadcastUri).append("]").toString(); @@ -146,4 +155,12 @@ public String getNetworkSegmentName() { public void setNetworkSegmentName(String networkSegmentName) { this.networkSegmentName = networkSegmentName; } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } diff --git a/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java b/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java index d43625c09a92..91f337c5f55b 100644 --- a/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/PortForwardingRuleTO.java @@ -21,8 +21,6 @@ import com.cloud.utils.net.NetUtils; import org.apache.commons.lang3.StringUtils; -import java.util.List; - /** * PortForwardingRuleTO specifies one port forwarding rule. * @@ -32,8 +30,6 @@ public class PortForwardingRuleTO extends FirewallRuleTO { String dstIp; int[] dstPortRange; - List sourceCidrList; - protected PortForwardingRuleTO() { super(); } diff --git a/api/src/main/java/com/cloud/agent/api/to/RemoteInstanceTO.java b/api/src/main/java/com/cloud/agent/api/to/RemoteInstanceTO.java index 18737c584b34..7daeb9649177 100644 --- a/api/src/main/java/com/cloud/agent/api/to/RemoteInstanceTO.java +++ b/api/src/main/java/com/cloud/agent/api/to/RemoteInstanceTO.java @@ -36,13 +36,17 @@ public class RemoteInstanceTO implements Serializable { private String vcenterPassword; private String vcenterHost; private String datacenterName; + private String clusterName; + private String hostName; public RemoteInstanceTO() { } - public RemoteInstanceTO(String instanceName) { + public RemoteInstanceTO(String instanceName, String clusterName, String hostName) { this.hypervisorType = Hypervisor.HypervisorType.VMware; this.instanceName = instanceName; + this.clusterName = clusterName; + this.hostName = hostName; } public RemoteInstanceTO(String instanceName, String instancePath, String vcenterHost, String vcenterUsername, String vcenterPassword, String datacenterName) { @@ -55,6 +59,12 @@ public RemoteInstanceTO(String instanceName, String instancePath, String vcenter this.datacenterName = datacenterName; } + public RemoteInstanceTO(String instanceName, String instancePath, String vcenterHost, String vcenterUsername, String vcenterPassword, String datacenterName, String clusterName, String hostName) { + this(instanceName, instancePath, vcenterHost, vcenterUsername, vcenterPassword, datacenterName); + this.clusterName = clusterName; + this.hostName = hostName; + } + public Hypervisor.HypervisorType getHypervisorType() { return this.hypervisorType; } @@ -82,4 +92,12 @@ public String getVcenterHost() { public String getDatacenterName() { return datacenterName; } + + public String getClusterName() { + return clusterName; + } + + public String getHostName() { + return hostName; + } } diff --git a/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java new file mode 100644 index 000000000000..5b22afdedd53 --- /dev/null +++ b/api/src/main/java/com/cloud/agent/api/to/VirtualMachineMetadataTO.java @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.agent.api.to; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class VirtualMachineMetadataTO { + // VM details + private final String name; + private final String internalName; + private final String displayName; + private final String instanceUuid; + private final Integer cpuCores; + private final Integer memory; + private final Long created; + private final Long started; + + // Owner details + private final String ownerDomainUuid; + private final String ownerDomainName; + private final String ownerAccountUuid; + private final String ownerAccountName; + private final String ownerProjectUuid; + private final String ownerProjectName; + + // Host and service offering + private final String serviceOfferingName; + private final List serviceOfferingHostTags; + + // zone, pod, and cluster details + private final String zoneName; + private final String zoneUuid; + private final String podName; + private final String podUuid; + private final String clusterName; + private final String clusterUuid; + + // resource tags + private final Map resourceTags; + + public VirtualMachineMetadataTO( + String name, String internalName, String displayName, String instanceUuid, Integer cpuCores, Integer memory, Long created, Long started, + String ownerDomainUuid, String ownerDomainName, String ownerAccountUuid, String ownerAccountName, String ownerProjectUuid, String ownerProjectName, + String serviceOfferingName, List serviceOfferingHostTags, + String zoneName, String zoneUuid, String podName, String podUuid, String clusterName, String clusterUuid, Map resourceTags) { + /* + * Something failed in the metadata shall not be a fatal error, the VM can still be started + * Thus, the unknown fields just get an explicit "unknown" value so it can be fixed in case + * there are bugs on some execution paths. + * */ + + this.name = (name != null) ? name : "unknown"; + this.internalName = (internalName != null) ? internalName : "unknown"; + this.displayName = (displayName != null) ? displayName : "unknown"; + this.instanceUuid = (instanceUuid != null) ? instanceUuid : "unknown"; + this.cpuCores = (cpuCores != null) ? cpuCores : -1; + this.memory = (memory != null) ? memory : -1; + this.created = (created != null) ? created : 0; + this.started = (started != null) ? started : 0; + this.ownerDomainUuid = (ownerDomainUuid != null) ? ownerDomainUuid : "unknown"; + this.ownerDomainName = (ownerDomainName != null) ? ownerDomainName : "unknown"; + this.ownerAccountUuid = (ownerAccountUuid != null) ? ownerAccountUuid : "unknown"; + this.ownerAccountName = (ownerAccountName != null) ? ownerAccountName : "unknown"; + this.ownerProjectUuid = (ownerProjectUuid != null) ? ownerProjectUuid : "unknown"; + this.ownerProjectName = (ownerProjectName != null) ? ownerProjectName : "unknown"; + this.serviceOfferingName = (serviceOfferingName != null) ? serviceOfferingName : "unknown"; + this.serviceOfferingHostTags = (serviceOfferingHostTags != null) ? serviceOfferingHostTags : new ArrayList<>(); + this.zoneName = (zoneName != null) ? zoneName : "unknown"; + this.zoneUuid = (zoneUuid != null) ? zoneUuid : "unknown"; + this.podName = (podName != null) ? podName : "unknown"; + this.podUuid = (podUuid != null) ? podUuid : "unknown"; + this.clusterName = (clusterName != null) ? clusterName : "unknown"; + this.clusterUuid = (clusterUuid != null) ? clusterUuid : "unknown"; + + this.resourceTags = (resourceTags != null) ? resourceTags : new HashMap<>(); + } + + public String getName() { + return name; + } + + public String getInternalName() { + return internalName; + } + + public String getDisplayName() { + return displayName; + } + + public String getInstanceUuid() { + return instanceUuid; + } + + public Integer getCpuCores() { + return cpuCores; + } + + public Integer getMemory() { + return memory; + } + + public Long getCreated() { return created; } + + public Long getStarted() { + return started; + } + + public String getOwnerDomainUuid() { + return ownerDomainUuid; + } + + public String getOwnerDomainName() { + return ownerDomainName; + } + + public String getOwnerAccountUuid() { + return ownerAccountUuid; + } + + public String getOwnerAccountName() { + return ownerAccountName; + } + + public String getOwnerProjectUuid() { + return ownerProjectUuid; + } + + public String getOwnerProjectName() { + return ownerProjectName; + } + + public String getserviceOfferingName() { + return serviceOfferingName; + } + + public List getserviceOfferingHostTags() { + return serviceOfferingHostTags; + } + + public String getZoneName() { + return zoneName; + } + + public String getZoneUuid() { + return zoneUuid; + } + + public String getPodName() { + return podName; + } + + public String getPodUuid() { + return podUuid; + } + + public String getClusterName() { + return clusterName; + } + + public String getClusterUuid() { + return clusterUuid; + } + + public Map getResourceTags() { return resourceTags; } +} 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 6f24b1cd6ca8..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 @@ -19,20 +19,22 @@ import java.util.List; import java.util.Map; import java.util.HashMap; +import java.util.stream.Collectors; import com.cloud.agent.api.LogLevel; import com.cloud.network.element.NetworkElement; import com.cloud.template.VirtualMachineTemplate.BootloaderType; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VmDetailConstants; public class VirtualMachineTO { private long id; private String name; private BootloaderType bootloader; private VirtualMachine.State state; - Type type; - int cpus; + private Type type; + private int cpus; /** 'speed' is still here since 4.0.X/4.1.X management servers do not support @@ -43,49 +45,52 @@ public class VirtualMachineTO { So this is here for backwards compatibility with 4.0.X/4.1.X management servers and newer agents. */ - Integer speed; - Integer minSpeed; - Integer maxSpeed; - - long minRam; - long maxRam; - String hostName; - String arch; - String os; - String platformEmulator; - String bootArgs; - String[] bootupScripts; - boolean enableHA; - boolean limitCpuUse; - boolean enableDynamicallyScaleVm; + private Integer speed; + private Integer minSpeed; + private Integer maxSpeed; + + private long minRam; + private long maxRam; + private long requestedRam; + private String hostName; + private String arch; + private String os; + private String platformEmulator; + private String bootArgs; + private String[] bootupScripts; + private boolean enableHA; + private boolean limitCpuUse; + private boolean enableDynamicallyScaleVm; @LogLevel(LogLevel.Log4jLevel.Off) - String vncPassword; - String vncAddr; - Map params; - String uuid; - String bootType; - String bootMode; - boolean enterHardwareSetup; - - DiskTO[] disks; - NicTO[] nics; - GPUDeviceTO gpuDevice; - Integer vcpuMaxLimit; - List vmData = null; - - String configDriveLabel = null; - String configDriveIsoRootFolder = null; - String configDriveIsoFile = null; - NetworkElement.Location configDriveLocation = NetworkElement.Location.SECONDARY; - - Double cpuQuotaPercentage = null; - - Map guestOsDetails = new HashMap(); - Map extraConfig = new HashMap<>(); - Map networkIdToNetworkNameMap = new HashMap<>(); - DeployAsIsInfoTO deployAsIsInfo; - String metadataManufacturer; - String metadataProductName; + private String vncPassword; + private String vncAddr; + private Map details; + private Map params; + private String uuid; + private String bootType; + private String bootMode; + private boolean enterHardwareSetup; + + private DiskTO[] disks; + private NicTO[] nics; + private GPUDeviceTO gpuDevice; + private Integer vcpuMaxLimit; + private List vmData = null; + + private String configDriveLabel = null; + private String configDriveIsoRootFolder = null; + private String configDriveIsoFile = null; + private NetworkElement.Location configDriveLocation = NetworkElement.Location.SECONDARY; + + private Double cpuQuotaPercentage = null; + + private Map guestOsDetails = new HashMap(); + private Map extraConfig = new HashMap<>(); + private Map networkIdToNetworkNameMap = new HashMap<>(); + private DeployAsIsInfoTO deployAsIsInfo; + private String metadataManufacturer; + private String metadataProductName; + private VirtualMachineMetadataTO metadata; public VirtualMachineTO(long id, String instanceName, VirtualMachine.Type type, int cpus, Integer speed, long minRam, long maxRam, BootloaderType bootloader, String os, boolean enableHA, boolean limitCpuUse, String vncPassword) { @@ -191,7 +196,11 @@ public Integer getMaxSpeed() { return maxSpeed; } - public boolean getLimitCpuUse() { + public boolean isEnableHA() { + return enableHA; + } + + public boolean isLimitCpuUse() { return limitCpuUse; } @@ -199,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; } @@ -256,6 +270,10 @@ public void setBootupScripts(String[] bootupScripts) { this.bootupScripts = bootupScripts; } + public void setEnableHA(boolean enableHA) { + this.enableHA = enableHA; + } + public DiskTO[] getDisks() { return disks; } @@ -289,11 +307,11 @@ public void setVncAddr(String vncAddr) { } public Map getDetails() { - return params; + return details; } public void setDetails(Map params) { - this.params = params; + this.details = params; } public String getUuid() { @@ -431,6 +449,42 @@ public void setDeployAsIsInfo(DeployAsIsInfoTO deployAsIsInfo) { this.deployAsIsInfo = deployAsIsInfo; } + public void setSpeed(Integer speed) { + this.speed = speed; + } + + public void setMinSpeed(Integer minSpeed) { + this.minSpeed = minSpeed; + } + + public void setMaxSpeed(Integer maxSpeed) { + this.maxSpeed = maxSpeed; + } + + public void setMinRam(long minRam) { + this.minRam = minRam; + } + + public void setMaxRam(long maxRam) { + this.maxRam = maxRam; + } + + public void setLimitCpuUse(boolean limitCpuUse) { + this.limitCpuUse = limitCpuUse; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + public void setExtraConfig(Map extraConfig) { + this.extraConfig = extraConfig; + } + public String getMetadataManufacturer() { return metadataManufacturer; } @@ -447,8 +501,28 @@ public void setMetadataProductName(String metadataProductName) { this.metadataProductName = metadataProductName; } + public VirtualMachineMetadataTO getMetadata() { + return metadata; + } + + public void setMetadata(VirtualMachineMetadataTO metadata) { + this.metadata = metadata; + } + @Override public String toString() { return String.format("VM {id: \"%s\", name: \"%s\", uuid: \"%s\", type: \"%s\"}", id, name, uuid, type); } + + public Map getExternalDetails() { + if (details == null) { + return new HashMap<>(); + } + return details.entrySet().stream() + .filter(entry -> entry.getKey().startsWith(VmDetailConstants.EXTERNAL_DETAIL_PREFIX)) + .collect(Collectors.toMap( + entry -> entry.getKey().substring(VmDetailConstants.EXTERNAL_DETAIL_PREFIX.length()), + Map.Entry::getValue + )); + } } 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/capacity/Capacity.java b/api/src/main/java/com/cloud/capacity/Capacity.java index a4e2c2a7f05d..4e584b18feee 100644 --- a/api/src/main/java/com/cloud/capacity/Capacity.java +++ b/api/src/main/java/com/cloud/capacity/Capacity.java @@ -34,13 +34,17 @@ public interface Capacity extends InternalIdentity, Identity { public static final short CAPACITY_TYPE_LOCAL_STORAGE = 9; public static final short CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET = 10; public static final short CAPACITY_TYPE_GPU = 19; + public static final short CAPACITY_TYPE_OBJECT_STORAGE = 20; + public static final short CAPACITY_TYPE_BACKUP_STORAGE = 21; public static final short CAPACITY_TYPE_CPU_CORE = 90; public static final List STORAGE_CAPACITY_TYPES = List.of(CAPACITY_TYPE_STORAGE, CAPACITY_TYPE_STORAGE_ALLOCATED, CAPACITY_TYPE_SECONDARY_STORAGE, - CAPACITY_TYPE_LOCAL_STORAGE); + CAPACITY_TYPE_LOCAL_STORAGE, + CAPACITY_TYPE_BACKUP_STORAGE, + CAPACITY_TYPE_OBJECT_STORAGE); public Long getHostOrPoolId(); diff --git a/api/src/main/java/com/cloud/configuration/ConfigurationService.java b/api/src/main/java/com/cloud/configuration/ConfigurationService.java index 97d4b42974b3..729f72b23ca2 100644 --- a/api/src/main/java/com/cloud/configuration/ConfigurationService.java +++ b/api/src/main/java/com/cloud/configuration/ConfigurationService.java @@ -17,18 +17,25 @@ package com.cloud.configuration; import java.util.List; +import java.util.Map; +import java.util.Objects; +import com.cloud.network.Network; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.command.admin.config.ResetCfgCmd; import org.apache.cloudstack.api.command.admin.config.UpdateCfgCmd; +import org.apache.cloudstack.api.command.admin.network.CloneNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.CreateGuestNetworkIpv6PrefixCmd; import org.apache.cloudstack.api.command.admin.network.CreateManagementNetworkIpRangeCmd; -import org.apache.cloudstack.api.command.admin.network.CreateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.DeleteGuestNetworkIpv6PrefixCmd; import org.apache.cloudstack.api.command.admin.network.DeleteManagementNetworkIpRangeCmd; import org.apache.cloudstack.api.command.admin.network.DeleteNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.ListGuestNetworkIpv6PrefixesCmd; +import org.apache.cloudstack.api.command.admin.network.NetworkOfferingBaseCmd; import org.apache.cloudstack.api.command.admin.network.UpdateNetworkOfferingCmd; import org.apache.cloudstack.api.command.admin.network.UpdatePodManagementNetworkIpRangeCmd; +import org.apache.cloudstack.api.command.admin.offering.CloneDiskOfferingCmd; +import org.apache.cloudstack.api.command.admin.offering.CloneServiceOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.CreateDiskOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.CreateServiceOfferingCmd; import org.apache.cloudstack.api.command.admin.offering.DeleteDiskOfferingCmd; @@ -101,39 +108,52 @@ public interface ConfigurationService { */ ServiceOffering createServiceOffering(CreateServiceOfferingCmd cmd); + /** + * Clones a service offering with optional parameter overrides + * + * @param cmd + * the command object that specifies the source offering ID and optional parameter overrides + * @return the newly created service offering cloned from source, null otherwise + */ + ServiceOffering cloneServiceOffering(CloneServiceOfferingCmd cmd); + + /** + * Clones a disk offering with optional parameter overrides + * + * @param cmd + * the command object that specifies the source offering ID and optional parameter overrides + * @return the newly created disk offering cloned from source, null otherwise + */ + DiskOffering cloneDiskOffering(CloneDiskOfferingCmd cmd); + + /** + * Clones a network offering with optional parameter overrides + * + * @param cmd + * the command object that specifies the source offering ID and optional parameter overrides + * @return the newly created network offering cloned from source, null otherwise + */ + NetworkOffering cloneNetworkOffering(CloneNetworkOfferingCmd cmd); + /** * Updates a service offering * - * @param serviceOfferingId - * @param userId - * @param name - * @param displayText - * @param offerHA - * @param useVirtualNetwork - * @param tags * @return updated service offering */ ServiceOffering updateServiceOffering(UpdateServiceOfferingCmd cmd); /** * Deletes a service offering - * - * @param userId - * @param serviceOfferingId */ boolean deleteServiceOffering(DeleteServiceOfferingCmd cmd); /** * Retrieve ID of domains for a service offering - * - * @param serviceOfferingId */ List getServiceOfferingDomains(Long serviceOfferingId); /** * Retrieve ID of domains for a service offering - * - * @param serviceOfferingId */ List getServiceOfferingZones(Long serviceOfferingId); @@ -143,7 +163,6 @@ public interface ConfigurationService { * @param cmd * - the command specifying diskOfferingId, name, description, tags * @return updated disk offering - * @throws */ DiskOffering updateDiskOffering(UpdateDiskOfferingCmd cmd); @@ -153,34 +172,22 @@ public interface ConfigurationService { * @param cmd * - the command specifying disk offering id * @return true or false - * @throws */ boolean deleteDiskOffering(DeleteDiskOfferingCmd cmd); /** * Creates a new disk offering - * - * @param domainId - * @param name - * @param description - * @param numGibibytes - * @param mirrored - * @param size * @return ID */ DiskOffering createDiskOffering(CreateDiskOfferingCmd cmd); /** * Retrieve ID of domains for a disk offering - * - * @param diskOfferingId */ List getDiskOfferingDomains(Long diskOfferingId); /** * Retrieve ID of domains for a disk offering - * - * @param diskOfferingId */ List getDiskOfferingZones(Long diskOfferingId); @@ -201,11 +208,10 @@ public interface ConfigurationService { * TODO * @param allocationState * TODO + * @param storageAccessGroups * @return the new pod if successful, null otherwise - * @throws - * @throws */ - Pod createPod(long zoneId, String name, String startIp, String endIp, String gateway, String netmask, String allocationState); + Pod createPod(long zoneId, String name, String startIp, String endIp, String gateway, String netmask, String allocationState, List storageAccessGroups); /** * Creates a mutual exclusive IP range in the pod with same gateway, netmask. @@ -223,8 +229,7 @@ public interface ConfigurationService { /** * Updates a mutually exclusive IP range in the pod. * @param cmd - The command specifying pod ID, current Start IP, current End IP, new Start IP, new End IP. - * @throws com.cloud.exception.ConcurrentOperationException - * @return Success + * @throws com.cloud.exception.ConcurrentOperationException when this pod is already being accessed */ void updatePodIpRange(UpdatePodManagementNetworkIpRangeCmd cmd) throws ConcurrentOperationException; @@ -245,9 +250,6 @@ public interface ConfigurationService { /** * Edits a pod in the database. Will not allow you to edit pods that are being used anywhere in the system. - * - * @param UpdatePodCmd - * api command */ Pod editPod(UpdatePodCmd cmd); @@ -257,17 +259,12 @@ public interface ConfigurationService { * @param cmd * - the command containing podId * @return true or false - * @throws , */ boolean deletePod(DeletePodCmd cmd); /** * Creates a new zone - * - * @param cmd * @return the zone if successful, null otherwise - * @throws - * @throws */ DataCenter createZone(CreateZoneCmd cmd); @@ -290,22 +287,7 @@ public interface ConfigurationService { * Adds a VLAN to the database, along with an IP address range. Can add three types of VLANs: (1) zone-wide VLANs on * the * virtual public network (2) pod-wide direct attached VLANs (3) account-specific direct attached VLANs - * - * @param userId - * @param vlanType - * - either "DomR" (VLAN for a virtual public network) or "DirectAttached" (VLAN for IPs that will be - * directly - * attached to UserVMs) - * @param zoneId - * @param accountId - * @param podId - * @param add - * @param vlanId - * @param gateway - * @param startIP - * @param endIP * @throws ResourceAllocationException TODO - * @throws * @return The new Vlan object */ Vlan createVlanAndPublicIpRange(CreateVlanIpRangeCmd cmd) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, @@ -320,9 +302,6 @@ Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOpera /** * Marks the account with the default zone-id. * - * @param accountName - * @param domainId - * @param defaultZoneId * @return The new account object */ Account markDefaultZone(String accountName, long domainId, long defaultZoneId); @@ -333,7 +312,7 @@ Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOpera boolean releasePublicIpRange(ReleasePublicIpRangeCmd cmd); - NetworkOffering createNetworkOffering(CreateNetworkOfferingCmd cmd); + NetworkOffering createNetworkOffering(NetworkOfferingBaseCmd cmd); NetworkOffering updateNetworkOffering(UpdateNetworkOfferingCmd cmd); @@ -344,14 +323,12 @@ Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOpera /** * Retrieve ID of domains for a network offering * - * @param networkOfferingId */ List getNetworkOfferingDomains(Long networkOfferingId); /** * Retrieve ID of domains for a network offering * - * @param networkOfferingId */ List getNetworkOfferingZones(Long networkOfferingId); @@ -372,4 +349,16 @@ Vlan updateVlanAndPublicIpRange(UpdateVlanIpRangeCmd cmd) throws ConcurrentOpera List listPortableIps(long id); Boolean isAccountAllowedToCreateOfferingsWithTags(IsAccountAllowedToCreateOfferingsWithTagsCmd cmd); + + public static final Map ProviderDetailKeyMap = Map.of( + Network.Provider.Nsx.getName(), ApiConstants.NSX_DETAIL_KEY, + Network.Provider.Netris.getName(), ApiConstants.NETRIS_DETAIL_KEY + ); + + public static boolean IsIpRangeForProvider(Network.Provider provider) { + if (Objects.isNull(provider)) { + return false; + } + return ProviderDetailKeyMap.containsKey(provider.getName()); + } } diff --git a/api/src/main/java/com/cloud/configuration/Resource.java b/api/src/main/java/com/cloud/configuration/Resource.java index bf8fca9d9051..97be7f9d64c5 100644 --- a/api/src/main/java/com/cloud/configuration/Resource.java +++ b/api/src/main/java/com/cloud/configuration/Resource.java @@ -21,7 +21,7 @@ public interface Resource { short RESOURCE_UNLIMITED = -1; String UNLIMITED = "Unlimited"; - enum ResourceType { // Primary and Secondary storage are allocated_storage and not the physical storage. + enum ResourceType { // All storage type resources are allocated_storage and not the physical storage. user_vm("user_vm", 0), public_ip("public_ip", 1), volume("volume", 2), @@ -33,7 +33,12 @@ enum ResourceType { // Primary and Secondary storage are allocated_storage and n cpu("cpu", 8), memory("memory", 9), primary_storage("primary_storage", 10), - secondary_storage("secondary_storage", 11); + secondary_storage("secondary_storage", 11), + backup("backup", 12), + backup_storage("backup_storage", 13), + bucket("bucket", 14), + object_storage("object_storage", 15), + gpu("gpu", 16); private String name; private int ordinal; @@ -62,6 +67,10 @@ public static ResourceType fromOrdinal(int ordinal) { } return null; } + + public static Boolean isStorageType(ResourceType type) { + return (type == primary_storage || type == secondary_storage || type == backup_storage || type == object_storage); + } } public static class ResourceOwnerType { diff --git a/api/src/main/java/com/cloud/cpu/CPU.java b/api/src/main/java/com/cloud/cpu/CPU.java index 3016e542db65..11b38b73da53 100644 --- a/api/src/main/java/com/cloud/cpu/CPU.java +++ b/api/src/main/java/com/cloud/cpu/CPU.java @@ -22,7 +22,8 @@ public class CPU { public enum CPUArch { x86("i686", 32), amd64("x86_64", 64), - arm64("aarch64", 64); + arm64("aarch64", 64), + s390x("s390x", 64); private final String type; private final int bits; diff --git a/api/src/main/java/com/cloud/dc/Pod.java b/api/src/main/java/com/cloud/dc/Pod.java index 1cbab36f3bd4..17c5b615d4b6 100644 --- a/api/src/main/java/com/cloud/dc/Pod.java +++ b/api/src/main/java/com/cloud/dc/Pod.java @@ -43,4 +43,6 @@ public interface Pod extends InfrastructureEntity, Grouping, Identity, InternalI AllocationState getAllocationState(); boolean getExternalDhcp(); + + String getStorageAccessGroups(); } diff --git a/api/src/main/java/com/cloud/deploy/DeploymentClusterPlanner.java b/api/src/main/java/com/cloud/deploy/DeploymentClusterPlanner.java index 2697311d2b94..9471c3d5c84c 100644 --- a/api/src/main/java/com/cloud/deploy/DeploymentClusterPlanner.java +++ b/api/src/main/java/com/cloud/deploy/DeploymentClusterPlanner.java @@ -62,11 +62,11 @@ public interface DeploymentClusterPlanner extends DeploymentPlanner { "vm.allocation.algorithm", "Advanced", "random", - "Order in which hosts within a cluster will be considered for VM/volume allocation. The value can be 'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', or 'firstfitleastconsumed'.", + "Order in which hosts within a cluster will be considered for VM allocation. The value can be 'random', 'firstfit', 'userdispersing', or 'firstfitleastconsumed'.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, - "random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed"); + "random,firstfit,userdispersing,firstfitleastconsumed"); /** * This is called to determine list of possible clusters where a virtual diff --git a/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java b/api/src/main/java/com/cloud/deploy/DeploymentPlanner.java index 354f9cfaac53..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, userconcentratedpod_random, userconcentratedpod_firstfit; + random, firstfit, userdispersing, firstfitleastconsumed; } public enum PlannerResourceUsage { diff --git a/api/src/main/java/com/cloud/event/EventTypes.java b/api/src/main/java/com/cloud/event/EventTypes.java index 81ed185dae5a..f7d13343d469 100644 --- a/api/src/main/java/com/cloud/event/EventTypes.java +++ b/api/src/main/java/com/cloud/event/EventTypes.java @@ -27,17 +27,28 @@ import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupRepositoryService; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet; +import org.apache.cloudstack.dns.DnsRecord; +import org.apache.cloudstack.dns.DnsServer; +import org.apache.cloudstack.dns.DnsZone; +import org.apache.cloudstack.extension.Extension; +import org.apache.cloudstack.extension.ExtensionCustomAction; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.cloudstack.gpu.VgpuProfile; import org.apache.cloudstack.ha.HAConfig; +import org.apache.cloudstack.kms.HSMProfile; +import org.apache.cloudstack.kms.KMSKey; import org.apache.cloudstack.network.BgpPeer; import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap; import org.apache.cloudstack.quota.QuotaTariff; -import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.storage.object.ObjectStore; +import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.usage.Usage; -import org.apache.cloudstack.vm.schedule.VMSchedule; +import org.apache.cloudstack.schedule.ResourceSchedule; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterGuestIpv6Prefix; @@ -119,17 +130,18 @@ public class EventTypes { public static final String EVENT_VM_UNMANAGE = "VM.UNMANAGE"; public static final String EVENT_VM_RECOVER = "VM.RECOVER"; - // VM Schedule - public static final String EVENT_VM_SCHEDULE_CREATE = "VM.SCHEDULE.CREATE"; - public static final String EVENT_VM_SCHEDULE_UPDATE = "VM.SCHEDULE.UPDATE"; - public static final String EVENT_VM_SCHEDULE_DELETE = "VM.SCHEDULE.DELETE"; - + // VM Schedule action-execution events (fired when a scheduled action runs). public static final String EVENT_VM_SCHEDULE_START = "VM.SCHEDULE.START"; public static final String EVENT_VM_SCHEDULE_STOP = "VM.SCHEDULE.STOP"; public static final String EVENT_VM_SCHEDULE_REBOOT = "VM.SCHEDULE.REBOOT"; public static final String EVENT_VM_SCHEDULE_FORCE_STOP = "VM.SCHEDULE.FORCE.STOP"; public static final String EVENT_VM_SCHEDULE_FORCE_REBOOT = "VM.SCHEDULE.FORCE.REBOOT"; + // Generic Resource Schedule CRUD events (apply to all resource types). + public static final String EVENT_SCHEDULE_CREATE = "SCHEDULE.CREATE"; + public static final String EVENT_SCHEDULE_UPDATE = "SCHEDULE.UPDATE"; + public static final String EVENT_SCHEDULE_DELETE = "SCHEDULE.DELETE"; + // Domain Router public static final String EVENT_ROUTER_CREATE = "ROUTER.CREATE"; public static final String EVENT_ROUTER_DESTROY = "ROUTER.DESTROY"; @@ -265,6 +277,20 @@ public class EventTypes { public static final String EVENT_CA_CERTIFICATE_REVOKE = "CA.CERTIFICATE.REVOKE"; public static final String EVENT_CA_CERTIFICATE_PROVISION = "CA.CERTIFICATE.PROVISION"; + // KMS (Key Management Service) events + public static final String EVENT_KMS_KEY_WRAP = "KMS.KEY.WRAP"; + public static final String EVENT_KMS_KEY_UNWRAP = "KMS.KEY.UNWRAP"; + public static final String EVENT_KMS_KEY_CREATE = "KMS.KEY.CREATE"; + public static final String EVENT_KMS_KEY_UPDATE = "KMS.KEY.UPDATE"; + public static final String EVENT_KMS_KEY_ROTATE = "KMS.KEY.ROTATE"; + public static final String EVENT_KMS_KEY_DELETE = "KMS.KEY.DELETE"; + public static final String EVENT_VOLUME_MIGRATE_TO_KMS = "VOLUME.MIGRATE.TO.KMS"; + + // HSM Profile events + public static final String EVENT_HSM_PROFILE_CREATE = "HSM.PROFILE.CREATE"; + public static final String EVENT_HSM_PROFILE_UPDATE = "HSM.PROFILE.UPDATE"; + public static final String EVENT_HSM_PROFILE_DELETE = "HSM.PROFILE.DELETE"; + // Account events public static final String EVENT_ACCOUNT_ENABLE = "ACCOUNT.ENABLE"; public static final String EVENT_ACCOUNT_DISABLE = "ACCOUNT.DISABLE"; @@ -289,9 +315,12 @@ public class EventTypes { //registering userdata events public static final String EVENT_REGISTER_USER_DATA = "REGISTER.USER.DATA"; + public static final String EVENT_REGISTER_CNI_CONFIG = "REGISTER.CNI.CONFIG"; + public static final String EVENT_DELETE_CNI_CONFIG = "DELETE.CNI.CONFIG"; - //register for user API and secret keys + //user API and secret keys public static final String EVENT_REGISTER_FOR_SECRET_API_KEY = "REGISTER.USER.KEY"; + public static final String EVENT_DELETE_SECRET_API_KEY = "DELETE.USER.KEY"; public static final String API_KEY_ACCESS_UPDATE = "API.KEY.ACCESS.UPDATE"; // Template Events @@ -366,16 +395,34 @@ public class EventTypes { // Service Offerings public static final String EVENT_SERVICE_OFFERING_CREATE = "SERVICE.OFFERING.CREATE"; + public static final String EVENT_SERVICE_OFFERING_CLONE = "SERVICE.OFFERING.CLONE"; public static final String EVENT_SERVICE_OFFERING_EDIT = "SERVICE.OFFERING.EDIT"; public static final String EVENT_SERVICE_OFFERING_DELETE = "SERVICE.OFFERING.DELETE"; // Disk Offerings public static final String EVENT_DISK_OFFERING_CREATE = "DISK.OFFERING.CREATE"; + public static final String EVENT_DISK_OFFERING_CLONE = "DISK.OFFERING.CLONE"; public static final String EVENT_DISK_OFFERING_EDIT = "DISK.OFFERING.EDIT"; public static final String EVENT_DISK_OFFERING_DELETE = "DISK.OFFERING.DELETE"; + // GPU Cards + public static final String EVENT_GPU_CARD_CREATE = "GPU.CARD.CREATE"; + public static final String EVENT_GPU_CARD_EDIT = "GPU.CARD.EDIT"; + public static final String EVENT_GPU_CARD_DELETE = "GPU.CARD.DELETE"; + + // vGPU Profile + public static final String EVENT_VGPU_PROFILE_CREATE = "VGPU.PROFILE.CREATE"; + public static final String EVENT_VGPU_PROFILE_EDIT = "VGPU.PROFILE.EDIT"; + public static final String EVENT_VGPU_PROFILE_DELETE = "VGPU.PROFILE.DELETE"; + + // GPU Devices + public static final String EVENT_GPU_DEVICE_CREATE = "GPU.DEVICE.CREATE"; + public static final String EVENT_GPU_DEVICE_EDIT = "GPU.DEVICE.EDIT"; + public static final String EVENT_GPU_DEVICE_DELETE = "GPU.DEVICE.DELETE"; + // Network offerings public static final String EVENT_NETWORK_OFFERING_CREATE = "NETWORK.OFFERING.CREATE"; + public static final String EVENT_NETWORK_OFFERING_CLONE = "NETWORK.OFFERING.CLONE"; public static final String EVENT_NETWORK_OFFERING_ASSIGN = "NETWORK.OFFERING.ASSIGN"; public static final String EVENT_NETWORK_OFFERING_EDIT = "NETWORK.OFFERING.EDIT"; public static final String EVENT_NETWORK_OFFERING_REMOVE = "NETWORK.OFFERING.REMOVE"; @@ -465,6 +512,7 @@ public class EventTypes { public static final String EVENT_ENABLE_PRIMARY_STORAGE = "ENABLE.PS"; public static final String EVENT_DISABLE_PRIMARY_STORAGE = "DISABLE.PS"; public static final String EVENT_SYNC_STORAGE_POOL = "SYNC.STORAGE.POOL"; + public static final String EVENT_CONFIGURE_STORAGE_ACCESS = "CONFIGURE.STORAGE.ACCESS"; public static final String EVENT_CHANGE_STORAGE_POOL_SCOPE = "CHANGE.STORAGE.POOL.SCOPE"; // VPN @@ -479,6 +527,7 @@ public class EventTypes { public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE = "VPN.S2S.CUSTOMER.GATEWAY.CREATE"; public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE = "VPN.S2S.CUSTOMER.GATEWAY.DELETE"; public static final String EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE = "VPN.S2S.CUSTOMER.GATEWAY.UPDATE"; + public static final String EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS = "VPN.S2S.GATEWAY.OBSOLETE.PARAMS"; public static final String EVENT_S2S_VPN_CONNECTION_CREATE = "VPN.S2S.CONNECTION.CREATE"; public static final String EVENT_S2S_VPN_CONNECTION_DELETE = "VPN.S2S.CONNECTION.DELETE"; public static final String EVENT_S2S_VPN_CONNECTION_RESET = "VPN.S2S.CONNECTION.RESET"; @@ -496,6 +545,8 @@ public class EventTypes { public static final String EVENT_ZONE_VLAN_ASSIGN = "ZONE.VLAN.ASSIGN"; public static final String EVENT_ZONE_VLAN_RELEASE = "ZONE.VLAN.RELEASE"; + public static final String EVENT_ZONE_VXLAN_ASSIGN = "ZONE.VXLAN.ASSIGN"; + public static final String EVENT_ZONE_VXLAN_RELEASE = "ZONE.VXLAN.RELEASE"; // Projects public static final String EVENT_PROJECT_CREATE = "PROJECT.CREATE"; @@ -556,6 +607,7 @@ public class EventTypes { // Network ACL public static final String EVENT_NETWORK_ACL_CREATE = "NETWORK.ACL.CREATE"; + public static final String EVENT_NETWORK_ACL_IMPORT = "NETWORK.ACL.IMPORT"; public static final String EVENT_NETWORK_ACL_DELETE = "NETWORK.ACL.DELETE"; public static final String EVENT_NETWORK_ACL_REPLACE = "NETWORK.ACL.REPLACE"; public static final String EVENT_NETWORK_ACL_UPDATE = "NETWORK.ACL.UPDATE"; @@ -570,6 +622,7 @@ public class EventTypes { // VPC offerings public static final String EVENT_VPC_OFFERING_CREATE = "VPC.OFFERING.CREATE"; + public static final String EVENT_VPC_OFFERING_CLONE = "VPC.OFFERING.CLONE"; public static final String EVENT_VPC_OFFERING_UPDATE = "VPC.OFFERING.UPDATE"; public static final String EVENT_VPC_OFFERING_DELETE = "VPC.OFFERING.DELETE"; @@ -602,16 +655,20 @@ public class EventTypes { // Backup and Recovery events public static final String EVENT_VM_BACKUP_IMPORT_OFFERING = "BACKUP.IMPORT.OFFERING"; + public static final String EVENT_VM_BACKUP_OFFERING_CLONE = "BACKUP.OFFERING.CLONE"; public static final String EVENT_VM_BACKUP_OFFERING_ASSIGN = "BACKUP.OFFERING.ASSIGN"; public static final String EVENT_VM_BACKUP_OFFERING_REMOVE = "BACKUP.OFFERING.REMOVE"; public static final String EVENT_VM_BACKUP_CREATE = "BACKUP.CREATE"; public static final String EVENT_VM_BACKUP_RESTORE = "BACKUP.RESTORE"; public static final String EVENT_VM_BACKUP_DELETE = "BACKUP.DELETE"; + public static final String EVENT_VM_BACKUP_OFFERING_REMOVED_AND_BACKUPS_DELETED = "BACKUP.OFFERING.BACKUPS.DEL"; public static final String EVENT_VM_BACKUP_RESTORE_VOLUME_TO_VM = "BACKUP.RESTORE.VOLUME.TO.VM"; public static final String EVENT_VM_BACKUP_SCHEDULE_CONFIGURE = "BACKUP.SCHEDULE.CONFIGURE"; public static final String EVENT_VM_BACKUP_SCHEDULE_DELETE = "BACKUP.SCHEDULE.DELETE"; public static final String EVENT_VM_BACKUP_USAGE_METRIC = "BACKUP.USAGE.METRIC"; public static final String EVENT_VM_BACKUP_EDIT = "BACKUP.OFFERING.EDIT"; + public static final String EVENT_VM_CREATE_FROM_BACKUP = "VM.CREATE.FROM.BACKUP"; + public static final String EVENT_SCREENSHOT_DOWNLOAD = "BACKUP.VALIDATION.SCREENSHOT.DOWNLOAD"; // external network device events public static final String EVENT_EXTERNAL_NVP_CONTROLLER_ADD = "PHYSICAL.NVPCONTROLLER.ADD"; @@ -640,6 +697,7 @@ public class EventTypes { public static final String EVENT_AUTOSCALEVMGROUP_DISABLE = "AUTOSCALEVMGROUP.DISABLE"; public static final String EVENT_AUTOSCALEVMGROUP_SCALEDOWN = "AUTOSCALEVMGROUP.SCALEDOWN"; public static final String EVENT_AUTOSCALEVMGROUP_SCALEUP = "AUTOSCALEVMGROUP.SCALEUP"; + public static final String EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE = "AUTOSCALEVMGROUP.SCHEDULE.UPDATE"; public static final String EVENT_BAREMETAL_DHCP_SERVER_ADD = "PHYSICAL.DHCP.ADD"; public static final String EVENT_BAREMETAL_DHCP_SERVER_DELETE = "PHYSICAL.DHCP.DELETE"; @@ -687,6 +745,9 @@ public class EventTypes { public static final String EVENT_EXTERNAL_OPENDAYLIGHT_CONFIGURE_CONTROLLER = "PHYSICAL.ODLCONTROLLER.CONFIGURE"; //Guest OS related events + public static final String EVENT_GUEST_OS_CATEGORY_ADD = "GUEST.OS.CATEGORY.ADD"; + public static final String EVENT_GUEST_OS_CATEGORY_DELETE = "GUEST.OS.CATEGORY.DELETE"; + public static final String EVENT_GUEST_OS_CATEGORY_UPDATE = "GUEST.OS.CATEGORY.UPDATE"; public static final String EVENT_GUEST_OS_ADD = "GUEST.OS.ADD"; public static final String EVENT_GUEST_OS_REMOVE = "GUEST.OS.REMOVE"; public static final String EVENT_GUEST_OS_UPDATE = "GUEST.OS.UPDATE"; @@ -739,6 +800,13 @@ public class EventTypes { //Purge resources public static final String EVENT_PURGE_EXPUNGED_RESOURCES = "PURGE.EXPUNGED.RESOURCES"; + // Management Server + public static final String EVENT_MS_MAINTENANCE_PREPARE = "MS.MAINTENANCE.PREPARE"; + public static final String EVENT_MS_MAINTENANCE_CANCEL = "MS.MAINTENANCE.CANCEL"; + public static final String EVENT_MS_SHUTDOWN_PREPARE = "MS.SHUTDOWN.PREPARE"; + public static final String EVENT_MS_SHUTDOWN_CANCEL = "MS.SHUTDOWN.CANCEL"; + public static final String EVENT_MS_SHUTDOWN = "MS.SHUTDOWN"; + // OBJECT STORE public static final String EVENT_OBJECT_STORE_CREATE = "OBJECT.STORE.CREATE"; public static final String EVENT_OBJECT_STORE_DELETE = "OBJECT.STORE.DELETE"; @@ -785,6 +853,52 @@ public class EventTypes { public static final String EVENT_SHAREDFS_EXPUNGE = "SHAREDFS.EXPUNGE"; public static final String EVENT_SHAREDFS_RECOVER = "SHAREDFS.RECOVER"; + // Resource Limit + public static final String EVENT_RESOURCE_LIMIT_UPDATE = "RESOURCE.LIMIT.UPDATE"; + + // Management Server + public static final String EVENT_MANAGEMENT_SERVER_REMOVE = "MANAGEMENT.SERVER.REMOVE"; + + // VM Lease + public static final String VM_LEASE_EXPIRED = "VM.LEASE.EXPIRED"; + public static final String VM_LEASE_DISABLED = "VM.LEASE.DISABLED"; + public static final String VM_LEASE_CANCELLED = "VM.LEASE.CANCELLED"; + public static final String VM_LEASE_EXPIRING = "VM.LEASE.EXPIRING"; + + // GUI Theme + public static final String EVENT_GUI_THEME_CREATE = "GUI.THEME.CREATE"; + public static final String EVENT_GUI_THEME_REMOVE = "GUI.THEME.REMOVE"; + public static final String EVENT_GUI_THEME_UPDATE = "GUI.THEME.UPDATE"; + + // Extension + public static final String EVENT_EXTENSION_CREATE = "EXTENSION.CREATE"; + public static final String EVENT_EXTENSION_UPDATE = "EXTENSION.UPDATE"; + public static final String EVENT_EXTENSION_DELETE = "EXTENSION.DELETE"; + public static final String EVENT_EXTENSION_RESOURCE_REGISTER = "EXTENSION.RESOURCE.REGISTER"; + public static final String EVENT_EXTENSION_RESOURCE_UNREGISTER = "EXTENSION.RESOURCE.UNREGISTER"; + public static final String EVENT_EXTENSION_RESOURCE_UPDATE = "EXTENSION.RESOURCE.UPDATE"; + public static final String EVENT_EXTENSION_CUSTOM_ACTION_ADD = "EXTENSION.CUSTOM.ACTION.ADD"; + public static final String EVENT_EXTENSION_CUSTOM_ACTION_UPDATE = "EXTENSION.CUSTOM.ACTION.UPDATE"; + public static final String EVENT_EXTENSION_CUSTOM_ACTION_DELETE = "EXTENSION.CUSTOM.ACTION.DELETE"; + + // Custom Action + public static final String EVENT_CUSTOM_ACTION = "CUSTOM.ACTION"; + + // Backup Repository + public static final String EVENT_BACKUP_REPOSITORY_ADD = "BACKUP.REPOSITORY.ADD"; + public static final String EVENT_BACKUP_REPOSITORY_UPDATE = "BACKUP.REPOSITORY.UPDATE"; + + // DNS Framework Events + public static final String EVENT_DNS_SERVER_ADD = "DNS.SERVER.ADD"; + public static final String EVENT_DNS_SERVER_UPDATE = "DNS.SERVER.UPDATE"; + public static final String EVENT_DNS_SERVER_DELETE = "DNS.SERVER.DELETE"; + public static final String EVENT_DNS_ZONE_CREATE = "DNS.ZONE.CREATE"; + public static final String EVENT_DNS_ZONE_UPDATE = "DNS.ZONE.UPDATE"; + public static final String EVENT_DNS_ZONE_DELETE = "DNS.ZONE.DELETE"; + public static final String EVENT_DNS_RECORD_CREATE = "DNS.RECORD.CREATE"; + public static final String EVENT_DNS_RECORD_DELETE = "DNS.RECORD.DELETE"; + public static final String EVENT_DNS_NAME_COLLISION = "DNS.NAME.COLLISION"; + static { // TODO: need a way to force author adding event types to declare the entity details as well, with out braking @@ -808,15 +922,18 @@ public class EventTypes { entityEventDetails.put(EVENT_VM_IMPORT, VirtualMachine.class); entityEventDetails.put(EVENT_VM_UNMANAGE, VirtualMachine.class); - // VMSchedule - entityEventDetails.put(EVENT_VM_SCHEDULE_CREATE, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_DELETE, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_UPDATE, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_START, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, VMSchedule.class); - entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, VMSchedule.class); + // VMSchedule action-execution events + entityEventDetails.put(EVENT_VM_SCHEDULE_START, ResourceSchedule.class); + entityEventDetails.put(EVENT_VM_SCHEDULE_STOP, ResourceSchedule.class); + entityEventDetails.put(EVENT_VM_SCHEDULE_REBOOT, ResourceSchedule.class); + entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_STOP, ResourceSchedule.class); + entityEventDetails.put(EVENT_VM_SCHEDULE_FORCE_REBOOT, ResourceSchedule.class); + entityEventDetails.put(EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE, ResourceSchedule.class); + + // Generic Resource Schedule + entityEventDetails.put(EVENT_SCHEDULE_CREATE, ResourceSchedule.class); + entityEventDetails.put(EVENT_SCHEDULE_UPDATE, ResourceSchedule.class); + entityEventDetails.put(EVENT_SCHEDULE_DELETE, ResourceSchedule.class); entityEventDetails.put(EVENT_ROUTER_CREATE, VirtualRouter.class); entityEventDetails.put(EVENT_ROUTER_DESTROY, VirtualRouter.class); @@ -935,6 +1052,20 @@ public class EventTypes { entityEventDetails.put(EVENT_VOLUME_RECOVER, Volume.class); entityEventDetails.put(EVENT_VOLUME_CHANGE_DISK_OFFERING, Volume.class); + // KMS Key Events + entityEventDetails.put(EVENT_KMS_KEY_CREATE, KMSKey.class); + entityEventDetails.put(EVENT_KMS_KEY_UPDATE, KMSKey.class); + entityEventDetails.put(EVENT_KMS_KEY_UNWRAP, KMSKey.class); + entityEventDetails.put(EVENT_KMS_KEY_WRAP, KMSKey.class); + entityEventDetails.put(EVENT_KMS_KEY_DELETE, KMSKey.class); + entityEventDetails.put(EVENT_KMS_KEY_ROTATE, KMSKey.class); + entityEventDetails.put(EVENT_VOLUME_MIGRATE_TO_KMS, KMSKey.class); + + // HSM Profile Events + entityEventDetails.put(EVENT_HSM_PROFILE_CREATE, HSMProfile.class); + entityEventDetails.put(EVENT_HSM_PROFILE_UPDATE, HSMProfile.class); + entityEventDetails.put(EVENT_HSM_PROFILE_DELETE, HSMProfile.class); + // Domains entityEventDetails.put(EVENT_DOMAIN_CREATE, Domain.class); entityEventDetails.put(EVENT_DOMAIN_DELETE, Domain.class); @@ -971,16 +1102,34 @@ public class EventTypes { // Service Offerings entityEventDetails.put(EVENT_SERVICE_OFFERING_CREATE, ServiceOffering.class); + entityEventDetails.put(EVENT_SERVICE_OFFERING_CLONE, ServiceOffering.class); entityEventDetails.put(EVENT_SERVICE_OFFERING_EDIT, ServiceOffering.class); entityEventDetails.put(EVENT_SERVICE_OFFERING_DELETE, ServiceOffering.class); // Disk Offerings entityEventDetails.put(EVENT_DISK_OFFERING_CREATE, DiskOffering.class); + entityEventDetails.put(EVENT_DISK_OFFERING_CLONE, DiskOffering.class); entityEventDetails.put(EVENT_DISK_OFFERING_EDIT, DiskOffering.class); entityEventDetails.put(EVENT_DISK_OFFERING_DELETE, DiskOffering.class); + // GPU Cards + entityEventDetails.put(EVENT_GPU_CARD_CREATE, GpuCard.class); + entityEventDetails.put(EVENT_GPU_CARD_EDIT, GpuCard.class); + entityEventDetails.put(EVENT_GPU_CARD_DELETE, GpuCard.class); + + // vGPU Profiles + entityEventDetails.put(EVENT_VGPU_PROFILE_CREATE, VgpuProfile.class); + entityEventDetails.put(EVENT_VGPU_PROFILE_EDIT, VgpuProfile.class); + entityEventDetails.put(EVENT_VGPU_PROFILE_DELETE, VgpuProfile.class); + + // GPU Devices + entityEventDetails.put(EVENT_GPU_DEVICE_CREATE, GpuDevice.class); + entityEventDetails.put(EVENT_GPU_DEVICE_EDIT, GpuDevice.class); + entityEventDetails.put(EVENT_GPU_DEVICE_DELETE, GpuDevice.class); + // Network offerings entityEventDetails.put(EVENT_NETWORK_OFFERING_CREATE, NetworkOffering.class); + entityEventDetails.put(EVENT_NETWORK_OFFERING_CLONE, NetworkOffering.class); entityEventDetails.put(EVENT_NETWORK_OFFERING_ASSIGN, NetworkOffering.class); entityEventDetails.put(EVENT_NETWORK_OFFERING_EDIT, NetworkOffering.class); entityEventDetails.put(EVENT_NETWORK_OFFERING_REMOVE, NetworkOffering.class); @@ -1064,6 +1213,7 @@ public class EventTypes { entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_CREATE, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_DELETE, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CUSTOMER_GATEWAY_UPDATE, Site2SiteCustomerGateway.class); + entityEventDetails.put(EVENT_S2S_VPN_GATEWAY_OBSOLETE_PARAMS, Site2SiteCustomerGateway.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_CREATE, Site2SiteVpnConnection.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_DELETE, Site2SiteVpnConnection.class); entityEventDetails.put(EVENT_S2S_VPN_CONNECTION_RESET, Site2SiteVpnConnection.class); @@ -1230,6 +1380,12 @@ public class EventTypes { entityEventDetails.put(EVENT_UPDATE_IMAGE_STORE_ACCESS_STATE, ImageStore.class); entityEventDetails.put(EVENT_LIVE_PATCH_SYSTEMVM, "SystemVMs"); + entityEventDetails.put(EVENT_MS_MAINTENANCE_PREPARE, "ManagementServer"); + entityEventDetails.put(EVENT_MS_MAINTENANCE_CANCEL, "ManagementServer"); + entityEventDetails.put(EVENT_MS_SHUTDOWN_PREPARE, "ManagementServer"); + entityEventDetails.put(EVENT_MS_SHUTDOWN_CANCEL, "ManagementServer"); + entityEventDetails.put(EVENT_MS_SHUTDOWN, "ManagementServer"); + //Object Store entityEventDetails.put(EVENT_OBJECT_STORE_CREATE, ObjectStore.class); entityEventDetails.put(EVENT_OBJECT_STORE_UPDATE, ObjectStore.class); @@ -1273,6 +1429,45 @@ public class EventTypes { entityEventDetails.put(EVENT_SHAREDFS_DESTROY, SharedFS.class); entityEventDetails.put(EVENT_SHAREDFS_EXPUNGE, SharedFS.class); entityEventDetails.put(EVENT_SHAREDFS_RECOVER, SharedFS.class); + + // Management Server + entityEventDetails.put(EVENT_MANAGEMENT_SERVER_REMOVE, "ManagementServer"); + + // VM Lease + entityEventDetails.put(VM_LEASE_EXPIRED, VirtualMachine.class); + entityEventDetails.put(VM_LEASE_EXPIRING, VirtualMachine.class); + entityEventDetails.put(VM_LEASE_DISABLED, VirtualMachine.class); + entityEventDetails.put(VM_LEASE_CANCELLED, VirtualMachine.class); + + // GUI theme + entityEventDetails.put(EVENT_GUI_THEME_CREATE, "GuiTheme"); + entityEventDetails.put(EVENT_GUI_THEME_REMOVE, "GuiTheme"); + entityEventDetails.put(EVENT_GUI_THEME_UPDATE, "GuiTheme"); + + // Extension + entityEventDetails.put(EVENT_EXTENSION_CREATE, Extension.class); + entityEventDetails.put(EVENT_EXTENSION_UPDATE, Extension.class); + entityEventDetails.put(EVENT_EXTENSION_DELETE, Extension.class); + entityEventDetails.put(EVENT_EXTENSION_RESOURCE_REGISTER, Extension.class); + entityEventDetails.put(EVENT_EXTENSION_RESOURCE_UNREGISTER, Extension.class); + entityEventDetails.put(EVENT_EXTENSION_CUSTOM_ACTION_ADD, ExtensionCustomAction.class); + entityEventDetails.put(EVENT_EXTENSION_CUSTOM_ACTION_UPDATE, ExtensionCustomAction.class); + entityEventDetails.put(EVENT_EXTENSION_CUSTOM_ACTION_DELETE, ExtensionCustomAction.class); + + // Backup Repository + entityEventDetails.put(EVENT_BACKUP_REPOSITORY_ADD, BackupRepositoryService.class); + entityEventDetails.put(EVENT_BACKUP_REPOSITORY_UPDATE, BackupRepositoryService.class); + + // DNS Framework Events + entityEventDetails.put(EVENT_DNS_SERVER_ADD, DnsServer.class); + entityEventDetails.put(EVENT_DNS_SERVER_UPDATE, DnsServer.class); + entityEventDetails.put(EVENT_DNS_SERVER_DELETE, DnsServer.class); + entityEventDetails.put(EVENT_DNS_ZONE_CREATE, DnsZone.class); + entityEventDetails.put(EVENT_DNS_ZONE_UPDATE, DnsZone.class); + entityEventDetails.put(EVENT_DNS_ZONE_DELETE, DnsZone.class); + entityEventDetails.put(EVENT_DNS_RECORD_CREATE, DnsRecord.class); + entityEventDetails.put(EVENT_DNS_RECORD_DELETE, DnsRecord.class); + } public static boolean isNetworkEvent(String eventType) { diff --git a/api/src/main/java/com/cloud/exception/OperationTimedoutException.java b/api/src/main/java/com/cloud/exception/OperationTimedoutException.java index fe27408eb4e3..66b607100d97 100644 --- a/api/src/main/java/com/cloud/exception/OperationTimedoutException.java +++ b/api/src/main/java/com/cloud/exception/OperationTimedoutException.java @@ -40,7 +40,7 @@ public class OperationTimedoutException extends CloudException { boolean _isActive; public OperationTimedoutException(Command[] cmds, long agentId, long seqId, int time, boolean isActive) { - super("Commands " + seqId + " to Host " + agentId + " timed out after " + time); + super("Commands " + seqId + " to Host " + agentId + " timed out after " + time + " secs"); _agentId = agentId; _seqId = seqId; _time = time; 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/host/Host.java b/api/src/main/java/com/cloud/host/Host.java index 4672b3027769..c110e4ca94e1 100644 --- a/api/src/main/java/com/cloud/host/Host.java +++ b/api/src/main/java/com/cloud/host/Host.java @@ -57,9 +57,15 @@ public static String[] toStrings(Host.Type... types) { String HOST_UEFI_ENABLE = "host.uefi.enable"; String HOST_VOLUME_ENCRYPTION = "host.volume.encryption"; String HOST_INSTANCE_CONVERSION = "host.instance.conversion"; + String HOST_VDDK_SUPPORT = "host.vddk.support"; + String HOST_VDDK_LIB_DIR = "vddk.lib.dir"; + String HOST_VDDK_VERSION = "host.vddk.version"; String HOST_OVFTOOL_VERSION = "host.ovftool.version"; String HOST_VIRTV2V_VERSION = "host.virtv2v.version"; String HOST_SSH_PORT = "host.ssh.port"; + String HOST_CDROM_MAX_COUNT = "host.cdrom.max.count"; + String GUEST_OS_CATEGORY_ID = "guest.os.category.id"; + String GUEST_OS_RULE = "guest.os.rule"; int DEFAULT_SSH_PORT = 22; @@ -183,6 +189,8 @@ public static String[] toStrings(Host.Type... types) { */ Long getManagementServerId(); + Long getLastManagementServerId(); + /* *@return removal date */ @@ -217,4 +225,6 @@ public static String[] toStrings(Host.Type... types) { ResourceState getResourceState(); CPU.CPUArch getArch(); + + String getStorageAccessGroups(); } diff --git a/api/src/main/java/com/cloud/host/HostStats.java b/api/src/main/java/com/cloud/host/HostStats.java index d14794401fa3..0e72b5f2d9d0 100644 --- a/api/src/main/java/com/cloud/host/HostStats.java +++ b/api/src/main/java/com/cloud/host/HostStats.java @@ -36,5 +36,4 @@ public interface HostStats { public HostStats getHostStats(); public double getLoadAverage(); - // public double getXapiMemoryUsageKBs(); } diff --git a/api/src/main/java/com/cloud/host/Status.java b/api/src/main/java/com/cloud/host/Status.java index 5dc82bbfaefa..af6af82e9739 100644 --- a/api/src/main/java/com/cloud/host/Status.java +++ b/api/src/main/java/com/cloud/host/Status.java @@ -127,6 +127,7 @@ public static String[] toStrings(Status... states) { s_fsm.addTransition(Status.Connecting, Event.HostDown, Status.Down); s_fsm.addTransition(Status.Connecting, Event.Ping, Status.Connecting); s_fsm.addTransition(Status.Connecting, Event.ManagementServerDown, Status.Disconnected); + s_fsm.addTransition(Status.Connecting, Event.StartAgentRebalance, Status.Rebalancing); s_fsm.addTransition(Status.Connecting, Event.AgentDisconnected, Status.Alert); s_fsm.addTransition(Status.Up, Event.PingTimeout, Status.Alert); s_fsm.addTransition(Status.Up, Event.AgentDisconnected, Status.Alert); diff --git a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java index 5739daf98f78..1f8741d3b7b2 100644 --- a/api/src/main/java/com/cloud/hypervisor/Hypervisor.java +++ b/api/src/main/java/com/cloud/hypervisor/Hypervisor.java @@ -56,6 +56,7 @@ public enum Functionality { public static final HypervisorType Ovm3 = new HypervisorType("Ovm3", ImageFormat.RAW); public static final HypervisorType LXC = new HypervisorType("LXC"); public static final HypervisorType Custom = new HypervisorType("Custom", null, EnumSet.of(RootDiskSizeOverride)); + public static final HypervisorType External = new HypervisorType("External", null, EnumSet.of(RootDiskSizeOverride)); public static final HypervisorType Any = new HypervisorType("Any"); /*If you don't care about the hypervisor type*/ private final String name; private final ImageFormat imageFormat; diff --git a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java index 0c821b4e36c0..67db19b7cc54 100644 --- a/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java +++ b/api/src/main/java/com/cloud/hypervisor/HypervisorGuru.java @@ -20,6 +20,7 @@ import java.util.Map; import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupProvider; import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.agent.api.Command; @@ -94,10 +95,10 @@ public interface HypervisorGuru extends Adapter { Map getClusterSettings(long vmId); VirtualMachine importVirtualMachineFromBackup(long zoneId, long domainId, long accountId, long userId, - String vmInternalName, Backup backup) throws Exception; + String vmInternalName, Backup backup, BackupProvider backupProvider) throws Exception; boolean attachRestoredVolumeToVirtualMachine(long zoneId, String location, Backup.VolumeInfo volumeInfo, - VirtualMachine vm, long poolId, Backup backup) throws Exception; + VirtualMachine vm, long poolId, Backup backup, BackupProvider backupProvider) throws Exception; /** * Will generate commands to migrate a vm to a pool. For now this will only work for stopped VMs on Vmware. * diff --git a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java similarity index 81% rename from plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java rename to api/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java index 379a0db2ccd7..80f6a6045c72 100644 --- a/plugins/integrations/kubernetes-service/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesCluster.java @@ -44,6 +44,8 @@ enum Event { AutoscaleRequested, ScaleUpRequested, ScaleDownRequested, + AddNodeRequested, + RemoveNodeRequested, UpgradeRequested, OperationSucceeded, OperationFailed, @@ -60,6 +62,8 @@ enum State { Scaling("Transient state in which resources are either getting scaled up/down"), ScalingStoppedCluster("Transient state in which the service offerings of stopped clusters are getting scaled"), Upgrading("Transient state in which cluster is getting upgraded"), + Importing("Transient state in which additional nodes are added as worker nodes to a cluster"), + RemovingNodes("Transient state in which additional nodes are removed from a cluster"), Alert("State to represent Kubernetes clusters which are not in expected desired state (operationally in active control place, stopped cluster VM's etc)."), Recovering("State in which Kubernetes cluster is recovering from alert state"), Destroyed("End state of Kubernetes cluster in which all resources are destroyed, cluster will not be usable further"), @@ -84,6 +88,8 @@ enum State { s_fsm.addTransition(State.Stopping, Event.OperationFailed, State.Alert); s_fsm.addTransition(State.Stopped, Event.StartRequested, State.Starting); + s_fsm.addTransition(State.Stopped, Event.OperationSucceeded, State.Stopped); + s_fsm.addTransition(State.Running, Event.OperationSucceeded, State.Running); s_fsm.addTransition(State.Running, Event.FaultsDetected, State.Alert); @@ -92,7 +98,7 @@ enum State { s_fsm.addTransition(State.Running, Event.ScaleDownRequested, State.Scaling); s_fsm.addTransition(State.Stopped, Event.ScaleUpRequested, State.ScalingStoppedCluster); s_fsm.addTransition(State.Scaling, Event.OperationSucceeded, State.Running); - s_fsm.addTransition(State.Scaling, Event.OperationFailed, State.Alert); + s_fsm.addTransition(State.Scaling, Event.OperationFailed, State.Running); s_fsm.addTransition(State.ScalingStoppedCluster, Event.OperationSucceeded, State.Stopped); s_fsm.addTransition(State.ScalingStoppedCluster, Event.OperationFailed, State.Alert); @@ -100,6 +106,17 @@ enum State { s_fsm.addTransition(State.Upgrading, Event.OperationSucceeded, State.Running); s_fsm.addTransition(State.Upgrading, Event.OperationFailed, State.Alert); + s_fsm.addTransition(State.Running, Event.AddNodeRequested, State.Importing); + s_fsm.addTransition(State.Alert, Event.AddNodeRequested, State.Importing); + s_fsm.addTransition(State.Importing, Event.OperationSucceeded, State.Running); + s_fsm.addTransition(State.Importing, Event.OperationFailed, State.Running); + s_fsm.addTransition(State.Alert, Event.OperationSucceeded, State.Running); + + s_fsm.addTransition(State.Running, Event.RemoveNodeRequested, State.RemovingNodes); + s_fsm.addTransition(State.Alert, Event.RemoveNodeRequested, State.RemovingNodes); + s_fsm.addTransition(State.RemovingNodes, Event.OperationSucceeded, State.Running); + s_fsm.addTransition(State.RemovingNodes, Event.OperationFailed, State.Running); + s_fsm.addTransition(State.Alert, Event.RecoveryRequested, State.Recovering); s_fsm.addTransition(State.Recovering, Event.OperationSucceeded, State.Running); s_fsm.addTransition(State.Recovering, Event.OperationFailed, State.Alert); @@ -146,4 +163,14 @@ enum State { Long getMaxSize(); Long getSecurityGroupId(); ClusterType getClusterType(); + Long getControlNodeServiceOfferingId(); + Long getWorkerNodeServiceOfferingId(); + Long getEtcdNodeServiceOfferingId(); + Long getControlNodeTemplateId(); + Long getWorkerNodeTemplateId(); + Long getEtcdNodeTemplateId(); + Long getEtcdNodeCount(); + Long getCniConfigId(); + String getCniConfigDetails(); + boolean isCsiEnabled(); } diff --git a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java index 84e898528b5f..5a6eaa3f7b9a 100644 --- a/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java +++ b/api/src/main/java/com/cloud/kubernetes/cluster/KubernetesServiceHelper.java @@ -18,14 +18,26 @@ import org.apache.cloudstack.acl.ControlledEntity; +import java.util.List; +import java.util.Map; + import com.cloud.user.Account; import com.cloud.uservm.UserVm; import com.cloud.utils.component.Adapter; public interface KubernetesServiceHelper extends Adapter { + enum KubernetesClusterNodeType { + CONTROL, WORKER, ETCD, DEFAULT + } + ControlledEntity findByUuid(String uuid); ControlledEntity findByVmId(long vmId); void checkVmCanBeDestroyed(UserVm userVm); + void checkVmAffinityGroupsCanBeUpdated(UserVm userVm); + boolean isValidNodeType(String nodeType); + Map getServiceOfferingNodeTypeMap(Map> serviceOfferingNodeTypeMap); + Map getTemplateNodeTypeMap(Map> templateNodeTypeMap); + Map> getAffinityGroupNodeTypeMap(Map> affinityGroupNodeTypeMap); void cleanupForAccount(Account account); } diff --git a/api/src/main/java/com/cloud/network/IpAddress.java b/api/src/main/java/com/cloud/network/IpAddress.java index ae1af4505773..70d652b54e99 100644 --- a/api/src/main/java/com/cloud/network/IpAddress.java +++ b/api/src/main/java/com/cloud/network/IpAddress.java @@ -99,4 +99,5 @@ enum Purpose { boolean isForSystemVms(); + boolean isForRouter(); } diff --git a/api/src/main/java/com/cloud/network/Network.java b/api/src/main/java/com/cloud/network/Network.java index 43825cbda6a4..2f0bcdd5ef9a 100644 --- a/api/src/main/java/com/cloud/network/Network.java +++ b/api/src/main/java/com/cloud/network/Network.java @@ -116,6 +116,7 @@ class Service { public static final Service NetworkACL = new Service("NetworkACL", Capability.SupportedProtocols); public static final Service Connectivity = new Service("Connectivity", Capability.DistributedRouter, Capability.RegionLevelVpc, Capability.StretchedL2Subnet, Capability.NoVlan, Capability.PublicAccess); + public static final Service CustomAction = new Service("CustomAction"); private final String name; private final Capability[] caps; @@ -206,6 +207,8 @@ public static class Provider { public static final Provider Tungsten = new Provider("Tungsten", false); public static final Provider Nsx = new Provider("Nsx", false); + public static final Provider Netris = new Provider("Netris", false); + public static final Provider NetworkExtension = new Provider("NetworkExtension", false, true); private final String name; private final boolean isExternal; @@ -249,11 +252,47 @@ public static Provider getProvider(String providerName) { return null; } + /** Private constructor for transient (non-registered) providers. */ + private Provider(String name) { + this.name = name; + this.isExternal = false; + this.needCleanupOnShutdown = true; + // intentionally NOT added to supportedProviders + } + + /** + * Creates a transient (non-registered) {@link Provider} with the given name. + * + *

The new instance is not added to {@code supportedProviders}, so it + * will never be returned by {@link #getProvider(String)} and will not pollute the + * global provider registry. Use this for dynamic / extension-backed providers + * whose names are only known at runtime (e.g. NetworkOrchestrator extensions).

+ * + * @param name the provider name (typically the extension name) + * @return a transient {@link Provider} instance with the given name + */ + public static Provider createTransientProvider(String name) { + return new Provider(name); + } + @Override public String toString() { return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) .append("name", name) .toString(); } + + @Override + public boolean equals(Object obj) { + if (this == obj) return true; + if (!(obj instanceof Provider)) return false; + Provider provider = (Provider) obj; + return this.name.equals(provider.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } } public static class Capability { @@ -509,4 +548,6 @@ public void setIp6Address(String ip6Address) { Integer getPrivateMtu(); Integer getNetworkCidrSize(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/NetworkModel.java b/api/src/main/java/com/cloud/network/NetworkModel.java index 309595d746f2..7e1a07ebeb69 100644 --- a/api/src/main/java/com/cloud/network/NetworkModel.java +++ b/api/src/main/java/com/cloud/network/NetworkModel.java @@ -187,6 +187,8 @@ public interface NetworkModel { boolean canElementEnableIndividualServices(Provider provider); + boolean canElementEnableIndividualServicesByName(String providerName); + boolean areServicesSupportedInNetwork(long networkId, Service... services); boolean isNetworkSystem(Network network); @@ -237,6 +239,18 @@ public interface NetworkModel { String getDefaultGuestTrafficLabel(long dcId, HypervisorType vmware); + /** + * Resolves a provider name to a {@link Provider} instance. + * For known static providers, delegates to {@link Provider#getProvider(String)}. + * For dynamically-registered NetworkOrchestrator extension providers whose names + * are not in the static registry, returns a transient {@link Provider} with the + * given name so callers can still dispatch correctly. + * + * @param providerName the provider name from {@code ntwk_service_map} or similar + * @return a {@link Provider} instance, or {@code null} if not resolvable + */ + Provider resolveProvider(String providerName); + /** * @param providerName * @return @@ -309,6 +323,8 @@ public interface NetworkModel { NicProfile getNicProfile(VirtualMachine vm, long networkId, String broadcastUri); + NicProfile getNicProfile(VirtualMachine vm, Nic nic, DataCenter dataCenter); + Set getAvailableIps(Network network, String requestedIp); String getDomainNetworkDomain(long domainId, long zoneId); 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/NetworkRuleApplier.java b/api/src/main/java/com/cloud/network/NetworkRuleApplier.java index b9942e71eb26..69b712bc6ca2 100644 --- a/api/src/main/java/com/cloud/network/NetworkRuleApplier.java +++ b/api/src/main/java/com/cloud/network/NetworkRuleApplier.java @@ -21,8 +21,13 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.rules.FirewallRule; +import com.cloud.network.vpc.Vpc; public interface NetworkRuleApplier { - public boolean applyRules(Network network, FirewallRule.Purpose purpose, List rules) throws ResourceUnavailableException; + default boolean applyRules(Network network, FirewallRule.Purpose purpose, List rules) throws ResourceUnavailableException { + return applyRules(network, null, purpose, rules); + } + + boolean applyRules(Network network, Vpc vpc, FirewallRule.Purpose purpose, List rules) throws ResourceUnavailableException; } diff --git a/api/src/main/java/com/cloud/network/NetworkService.java b/api/src/main/java/com/cloud/network/NetworkService.java index cb72346678df..c32bb711c0f2 100644 --- a/api/src/main/java/com/cloud/network/NetworkService.java +++ b/api/src/main/java/com/cloud/network/NetworkService.java @@ -19,7 +19,6 @@ import java.util.List; import java.util.Map; -import com.cloud.dc.DataCenter; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.command.admin.address.ReleasePodIpCmdByAdmin; import org.apache.cloudstack.api.command.admin.network.DedicateGuestVlanRangeCmd; @@ -39,13 +38,16 @@ import org.apache.cloudstack.api.command.user.vm.ListNicsCmd; import org.apache.cloudstack.api.response.AcquirePodIpCmdResponse; import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; +import com.cloud.agent.api.to.NicTO; +import com.cloud.dc.DataCenter; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientAddressCapacityException; import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.Network.IpAddresses; import com.cloud.network.Network.Service; import com.cloud.network.Networks.TrafficType; @@ -57,7 +59,6 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.Nic; import com.cloud.vm.NicSecondaryIp; -import org.apache.cloudstack.network.element.InternalLoadBalancerElementService; /** * The NetworkService interface is the "public" api to entities that make requests to the orchestration engine @@ -231,7 +232,7 @@ Network createPrivateNetwork(String networkName, String displayText, long physic /** * Requests an IP address for the guest NIC */ - NicSecondaryIp allocateSecondaryGuestIP(long nicId, IpAddresses requestedIpPair) throws InsufficientAddressCapacityException; + NicSecondaryIp allocateSecondaryGuestIP(long nicId, IpAddresses requestedIpPair, String description) throws InsufficientAddressCapacityException; boolean releaseSecondaryIpFromNic(long ipAddressId); @@ -272,4 +273,12 @@ Network createPrivateNetwork(String networkName, String displayText, long physic InternalLoadBalancerElementService getInternalLoadBalancerElementByNetworkServiceProviderId(long networkProviderId); InternalLoadBalancerElementService getInternalLoadBalancerElementById(long providerId); List getInternalLoadBalancerElements(); + + boolean handleCksIsoOnNetworkVirtualRouter(Long virtualRouterId, boolean mount) throws ResourceUnavailableException; + + IpAddresses getIpAddressesFromIps(String ipAddress, String ip6Address, String macAddress); + + String getNicVlanValueForExternalVm(NicTO nic); + + Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId); } diff --git a/api/src/main/java/com/cloud/network/Networks.java b/api/src/main/java/com/cloud/network/Networks.java index 8e7399bb21d0..61a1c820723f 100644 --- a/api/src/main/java/com/cloud/network/Networks.java +++ b/api/src/main/java/com/cloud/network/Networks.java @@ -81,7 +81,11 @@ public String getValueFrom(URI uri) { return uri == null ? null : uri.getAuthority(); } }, - Vswitch("vs", String.class), LinkLocal(null, null), Vnet("vnet", Long.class), Storage("storage", Integer.class), Lswitch("lswitch", String.class) { + Vswitch("vs", String.class), + LinkLocal(null, null), + Vnet("vnet", Long.class), + Storage("storage", Integer.class), + Lswitch("lswitch", String.class) { @Override public URI toUri(T value) { try { @@ -99,7 +103,8 @@ public String getValueFrom(URI uri) { return uri == null ? null : uri.getSchemeSpecificPart(); } }, - Mido("mido", String.class), Pvlan("pvlan", String.class), + Mido("mido", String.class), + Pvlan("pvlan", String.class), Vxlan("vxlan", Long.class) { @Override public URI toUri(T value) { @@ -129,7 +134,8 @@ public URI toUri(T value) { UnDecided(null, null), OpenDaylight("opendaylight", String.class), TUNGSTEN("tf", String.class), - NSX("nsx", String.class); + NSX("nsx", String.class), + Netris("netris", String.class); private final String scheme; private final Class type; diff --git a/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java b/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java index 9676badb4e90..d3804cd29daf 100644 --- a/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java +++ b/api/src/main/java/com/cloud/network/PhysicalNetworkTrafficType.java @@ -41,4 +41,6 @@ public interface PhysicalNetworkTrafficType extends InternalIdentity, Identity { String getHypervNetworkLabel(); String getOvm3NetworkLabel(); + + String getVlan(); } diff --git a/api/src/main/java/com/cloud/network/RouterHealthCheckResult.java b/api/src/main/java/com/cloud/network/RouterHealthCheckResult.java index eb65ae9088ec..22a46ce9ecdf 100644 --- a/api/src/main/java/com/cloud/network/RouterHealthCheckResult.java +++ b/api/src/main/java/com/cloud/network/RouterHealthCheckResult.java @@ -26,7 +26,7 @@ public interface RouterHealthCheckResult { String getCheckType(); - boolean getCheckResult(); + VirtualNetworkApplianceService.RouterHealthStatus getCheckResult(); Date getLastUpdateTime(); diff --git a/api/src/main/java/com/cloud/network/SDNProviderNetworkRule.java b/api/src/main/java/com/cloud/network/SDNProviderNetworkRule.java new file mode 100644 index 000000000000..a22db4287dcd --- /dev/null +++ b/api/src/main/java/com/cloud/network/SDNProviderNetworkRule.java @@ -0,0 +1,358 @@ +// 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.network; + +import java.util.List; + +public class SDNProviderNetworkRule { + + protected long domainId; + protected long accountId; + protected long zoneId; + protected Long networkResourceId; + protected String networkResourceName; + protected boolean isVpcResource; + protected long vmId; + protected long ruleId; + protected String publicIp; + protected String vmIp; + protected String publicPort; + protected String privatePort; + protected String protocol; + protected String algorithm; + protected List sourceCidrList; + protected List destinationCidrList; + protected Integer icmpCode; + + protected Integer icmpType; + protected String trafficType; + protected Network.Service service; + + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getZoneId() { + return zoneId; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + public Long getNetworkResourceId() { + return networkResourceId; + } + + public void setNetworkResourceId(Long networkResourceId) { + this.networkResourceId = networkResourceId; + } + + public String getNetworkResourceName() { + return networkResourceName; + } + + public void setNetworkResourceName(String networkResourceName) { + this.networkResourceName = networkResourceName; + } + + public boolean isVpcResource() { + return isVpcResource; + } + + public void setVpcResource(boolean vpcResource) { + isVpcResource = vpcResource; + } + + public long getVmId() { + return vmId; + } + + public void setVmId(long vmId) { + this.vmId = vmId; + } + + public long getRuleId() { + return ruleId; + } + + public void setRuleId(long ruleId) { + this.ruleId = ruleId; + } + + public String getPublicIp() { + return publicIp; + } + + public void setPublicIp(String publicIp) { + this.publicIp = publicIp; + } + + public String getVmIp() { + return vmIp; + } + + public void setVmIp(String vmIp) { + this.vmIp = vmIp; + } + + public String getPublicPort() { + return publicPort; + } + + public void setPublicPort(String publicPort) { + this.publicPort = publicPort; + } + + public String getPrivatePort() { + return privatePort; + } + + public void setPrivatePort(String privatePort) { + this.privatePort = privatePort; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public String getAlgorithm() { + return algorithm; + } + + public Network.Service getService() { + return service; + } + + public void setService(Network.Service service) { + this.service = service; + } + + public Integer getIcmpCode() { + return icmpCode; + } + + public void setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + } + + public Integer getIcmpType() { + return icmpType; + } + + public void setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + } + + public List getSourceCidrList() { + return sourceCidrList; + } + + public void setSourceCidrList(List sourceCidrList) { + this.sourceCidrList = sourceCidrList; + } + + public List getDestinationCidrList() { + return destinationCidrList; + } + + public void setDestinationCidrList(List destinationCidrList) { + this.destinationCidrList = destinationCidrList; + } + + public String getTrafficType() { + return trafficType; + } + + public void setTrafficType(String trafficType) { + this.trafficType = trafficType; + } + + public static class Builder { + public long domainId; + public long accountId; + public long zoneId; + public Long networkResourceId; + public String networkResourceName; + public boolean isVpcResource; + public long vmId; + + public long ruleId; + public String publicIp; + public String vmIp; + public String publicPort; + public String privatePort; + public String protocol; + public String algorithm; + public List sourceCidrList; + public List destinationCidrList; + public String trafficType; + public Integer icmpType; + public Integer icmpCode; + public Network.Service service; + + public Builder() { + // Default constructor + } + + public Builder setDomainId(long domainId) { + this.domainId = domainId; + return this; + } + + public Builder setAccountId(long accountId) { + this.accountId = accountId; + return this; + } + + public Builder setZoneId(long zoneId) { + this.zoneId = zoneId; + return this; + } + + public Builder setNetworkResourceId(Long networkResourceId) { + this.networkResourceId = networkResourceId; + return this; + } + + public Builder setNetworkResourceName(String networkResourceName) { + this.networkResourceName = networkResourceName; + return this; + } + + public Builder setVpcResource(boolean isVpcResource) { + this.isVpcResource = isVpcResource; + return this; + } + + + public Builder setVmId(long vmId) { + this.vmId = vmId; + return this; + } + + public Builder setRuleId(long ruleId) { + this.ruleId = ruleId; + return this; + } + + public Builder setPublicIp(String publicIp) { + this.publicIp = publicIp; + return this; + } + + public Builder setVmIp(String vmIp) { + this.vmIp = vmIp; + return this; + } + + public Builder setPublicPort(String publicPort) { + this.publicPort = publicPort; + return this; + } + + public Builder setPrivatePort(String privatePort) { + this.privatePort = privatePort; + return this; + } + + public Builder setProtocol(String protocol) { + this.protocol = protocol; + return this; + } + + public Builder setAlgorithm(String algorithm) { + this.algorithm = algorithm; + return this; + } + + public Builder setTrafficType(String trafficType) { + this.trafficType = trafficType; + return this; + } + + public Builder setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + return this; + } + + public Builder setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + return this; + } + + public Builder setSourceCidrList(List sourceCidrList) { + this.sourceCidrList = sourceCidrList; + return this; + } + + public Builder setDestinationCidrList(List destinationCidrList) { + this.destinationCidrList = destinationCidrList; + return this; + } + + public Builder setService(Network.Service service) { + this.service = service; + return this; + } + + public SDNProviderNetworkRule build() { + SDNProviderNetworkRule rule = new SDNProviderNetworkRule(); + rule.setDomainId(this.domainId); + rule.setAccountId(this.accountId); + rule.setZoneId(this.zoneId); + rule.setNetworkResourceId(this.networkResourceId); + rule.setNetworkResourceName(this.networkResourceName); + rule.setVpcResource(this.isVpcResource); + rule.setVmId(this.vmId); + rule.setVmIp(this.vmIp); + rule.setPublicIp(this.publicIp); + rule.setPublicPort(this.publicPort); + rule.setPrivatePort(this.privatePort); + rule.setProtocol(this.protocol); + rule.setRuleId(this.ruleId); + rule.setAlgorithm(this.algorithm); + rule.setIcmpType(this.icmpType); + rule.setIcmpCode(this.icmpCode); + rule.setSourceCidrList(this.sourceCidrList); + rule.setDestinationCidrList(this.destinationCidrList); + rule.setTrafficType(this.trafficType); + rule.setService(service); + return rule; + } + } +} diff --git a/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java b/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java index 994df875f7d3..51036abe0609 100644 --- a/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java +++ b/api/src/main/java/com/cloud/network/Site2SiteVpnConnection.java @@ -24,7 +24,7 @@ public interface Site2SiteVpnConnection extends ControlledEntity, InternalIdentity, Displayable { enum State { - Pending, Connecting, Connected, Disconnected, Error, + Pending, Connecting, Connected, Disconnected, Error, Removed } @Override diff --git a/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java b/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java index cb92739d2837..a60f1d49336a 100644 --- a/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java +++ b/api/src/main/java/com/cloud/network/VirtualNetworkApplianceService.java @@ -87,4 +87,8 @@ void startRouterForHA(VirtualMachine vm, Map performRouterHealthChecks(long routerId); void collectNetworkStatistics(T router, Nic nic); + + enum RouterHealthStatus{ + SUCCESS, FAILED, WARNING, UNKNOWN; + } } diff --git a/api/src/main/java/com/cloud/network/as/AutoScaleService.java b/api/src/main/java/com/cloud/network/as/AutoScaleService.java index ceca4de68428..4aef10f8de9e 100644 --- a/api/src/main/java/com/cloud/network/as/AutoScaleService.java +++ b/api/src/main/java/com/cloud/network/as/AutoScaleService.java @@ -70,6 +70,8 @@ public interface AutoScaleService { Counter createCounter(CreateCounterCmd cmd); + Counter getCounter(long counterId); + boolean deleteCounter(long counterId) throws ResourceInUseException; List listCounters(ListCountersCmd cmd); diff --git a/api/src/main/java/com/cloud/network/element/DnsServiceProvider.java b/api/src/main/java/com/cloud/network/element/DnsServiceProvider.java index 7abce537221c..2942e76965a7 100644 --- a/api/src/main/java/com/cloud/network/element/DnsServiceProvider.java +++ b/api/src/main/java/com/cloud/network/element/DnsServiceProvider.java @@ -33,4 +33,6 @@ boolean configDnsSupportForSubnet(Network network, NicProfile nic, VirtualMachin throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException; boolean removeDnsSupportForSubnet(Network network) throws ResourceUnavailableException; + + default boolean removeDnsEntry(Network network, NicProfile nic, VirtualMachineProfile vmProfile) throws ResourceUnavailableException { return true; } } diff --git a/api/src/main/java/com/cloud/network/element/FirewallServiceProvider.java b/api/src/main/java/com/cloud/network/element/FirewallServiceProvider.java index c091142d9353..6b0f932e8c22 100644 --- a/api/src/main/java/com/cloud/network/element/FirewallServiceProvider.java +++ b/api/src/main/java/com/cloud/network/element/FirewallServiceProvider.java @@ -21,14 +21,20 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.Network; import com.cloud.network.rules.FirewallRule; +import com.cloud.network.vpc.Vpc; public interface FirewallServiceProvider extends NetworkElement { /** - * Apply rules - * @param network - * @param rules - * @return - * @throws ResourceUnavailableException + * Apply firewall rules in a network context. */ - boolean applyFWRules(Network network, List rules) throws ResourceUnavailableException; + default boolean applyFWRules(Network network, List rules) throws ResourceUnavailableException { + return false; + } + + /** + * Apply firewall rules in a VPC context. + */ + default boolean applyFWRulesInVPC(Vpc vpc, List rules) throws ResourceUnavailableException { + return false; + } } diff --git a/api/src/main/java/com/cloud/network/element/NetworkElement.java b/api/src/main/java/com/cloud/network/element/NetworkElement.java index fa67575edd35..67be7b9ba2e2 100644 --- a/api/src/main/java/com/cloud/network/element/NetworkElement.java +++ b/api/src/main/java/com/cloud/network/element/NetworkElement.java @@ -23,6 +23,7 @@ import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.IpAddress; import com.cloud.network.Network; import com.cloud.network.Network.Capability; import com.cloud.network.Network.Provider; @@ -87,6 +88,14 @@ boolean prepare(Network network, NicProfile nic, VirtualMachineProfile vm, Deplo boolean release(Network network, NicProfile nic, VirtualMachineProfile vm, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException; + /** + * Release IP from the network provider if reserved + * @param ipAddress + */ + default boolean releaseIp(IpAddress ipAddress) { + return true; + } + /** * The network is being shutdown. * @param network @@ -137,4 +146,8 @@ boolean shutdownProviderInstances(PhysicalNetworkServiceProvider provider, Reser * @return true/false */ boolean verifyServicesCombination(Set services); + + default boolean rollingRestartSupported() { + return true; + } } diff --git a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java b/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java index e99bc2fd416b..8dcc8b6d0a47 100644 --- a/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java +++ b/api/src/main/java/com/cloud/network/element/PortForwardingServiceProvider.java @@ -17,12 +17,40 @@ package com.cloud.network.element; import java.util.List; +import java.util.Objects; import com.cloud.exception.ResourceUnavailableException; import com.cloud.network.Network; +import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.PortForwardingRule; +import com.cloud.network.vpc.NetworkACLItem; public interface PortForwardingServiceProvider extends NetworkElement, IpDeployingRequester { + + static String getPublicPortRange(PortForwardingRule rule) { + return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ? + String.valueOf(rule.getSourcePortStart()) : + String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd())); + } + + static String getPrivatePFPortRange(PortForwardingRule rule) { + return rule.getDestinationPortStart() == rule.getDestinationPortEnd() ? + String.valueOf(rule.getDestinationPortStart()) : + String.valueOf(rule.getDestinationPortStart()).concat("-").concat(String.valueOf(rule.getDestinationPortEnd())); + } + + static String getPrivatePortRange(FirewallRule rule) { + return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ? + String.valueOf(rule.getSourcePortStart()) : + String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd())); + } + + static String getPrivatePortRangeForACLRule(NetworkACLItem rule) { + return Objects.equals(rule.getSourcePortStart(), rule.getSourcePortEnd()) ? + String.valueOf(rule.getSourcePortStart()) : + String.valueOf(rule.getSourcePortStart()).concat("-").concat(String.valueOf(rule.getSourcePortEnd())); + } + /** * Apply rules * @param network diff --git a/api/src/main/java/com/cloud/network/element/VpcProvider.java b/api/src/main/java/com/cloud/network/element/VpcProvider.java index 6debd1fbc2d8..fe8c8f8612f7 100644 --- a/api/src/main/java/com/cloud/network/element/VpcProvider.java +++ b/api/src/main/java/com/cloud/network/element/VpcProvider.java @@ -55,4 +55,8 @@ boolean implementVpc(Vpc vpc, DeployDestination dest, ReservationContext context boolean applyACLItemsToPrivateGw(PrivateGateway gateway, List rules) throws ResourceUnavailableException; boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address); + + default boolean updateVpc(Vpc vpc, String previousVpcName) { + return true; + } } diff --git a/api/src/main/java/com/cloud/network/guru/NetworkGuru.java b/api/src/main/java/com/cloud/network/guru/NetworkGuru.java index 7b81c75ed845..ced664e54a96 100644 --- a/api/src/main/java/com/cloud/network/guru/NetworkGuru.java +++ b/api/src/main/java/com/cloud/network/guru/NetworkGuru.java @@ -215,4 +215,8 @@ void reserve(NicProfile nic, Network network, VirtualMachineProfile vm, DeployDe default boolean isSlaacV6Only() { return true; } + + default boolean update(Network network, String prevNetworkName) { + return true; + } } diff --git a/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java b/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java index 46f17237e029..b7fe3b26761c 100644 --- a/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java +++ b/api/src/main/java/com/cloud/network/lb/LoadBalancingRulesService.java @@ -41,13 +41,23 @@ public interface LoadBalancingRulesService { /** * Create a load balancer rule from the given ipAddress/port to the given private port + * @param xId an existing UUID for this rule (for instance a device generated one) + * @param name + * @param description + * @param srcPortStart + * @param srcPortEnd + * @param defPortStart + * @param defPortEnd + * @param ipAddrId + * @param protocol + * @param algorithm + * @param networkId + * @param lbOwnerId * @param openFirewall - * TODO - * @param forDisplay TODO - * @param cmd - * the command specifying the ip address, public port, protocol, private port, and algorithm - * + * @param lbProtocol + * @param forDisplay * @return the newly created LoadBalancerVO if successful, null otherwise + * @throws NetworkRuleConflictException * @throws InsufficientAddressCapacityException */ LoadBalancer createPublicLoadBalancerRule(String xId, String name, String description, int srcPortStart, int srcPortEnd, int defPortStart, int defPortEnd, @@ -98,7 +108,7 @@ LoadBalancer createPublicLoadBalancerRule(String xId, String name, String descri /** * Assign a virtual machine or list of virtual machines, or Map of to a load balancer. */ - boolean assignToLoadBalancer(long lbRuleId, List vmIds, Map> vmIdIpMap, boolean isAutoScaleVM); + boolean assignToLoadBalancer(long lbRuleId, List vmIds, Map> vmIdIpMap, Map vmIdNetworkMap, boolean isAutoScaleVM); boolean assignSSLCertToLoadBalancerRule(Long lbRuleId, String certName, String publicCert, String privateKey); @@ -106,7 +116,7 @@ LoadBalancer createPublicLoadBalancerRule(String xId, String name, String descri boolean applyLoadBalancerConfig(long lbRuleId) throws ResourceUnavailableException; - boolean assignCertToLoadBalancer(long lbRuleId, Long certId); + boolean assignCertToLoadBalancer(long lbRuleId, Long certId, boolean isForced); boolean removeCertFromLoadBalancer(long lbRuleId); diff --git a/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java b/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java new file mode 100644 index 000000000000..afc21f7f511b --- /dev/null +++ b/api/src/main/java/com/cloud/network/netris/NetrisLbBackend.java @@ -0,0 +1,41 @@ +// 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.network.netris; + +public class NetrisLbBackend { + private long vmId; + private String vmIp; + private int port; + + public NetrisLbBackend(long vmId, String vmIp, int port) { + this.vmId = vmId; + this.vmIp = vmIp; + this.port = port; + } + + public long getVmId() { + return vmId; + } + + public String getVmIp() { + return vmIp; + } + + public int getPort() { + return port; + } +} diff --git a/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java b/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java new file mode 100644 index 000000000000..211517ead491 --- /dev/null +++ b/api/src/main/java/com/cloud/network/netris/NetrisNetworkRule.java @@ -0,0 +1,108 @@ +// 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.network.netris; + +import com.cloud.network.SDNProviderNetworkRule; + + +import java.util.List; + +public class NetrisNetworkRule { + public enum NetrisRuleAction { + PERMIT, DENY + } + + private SDNProviderNetworkRule baseRule; + private NetrisRuleAction aclAction; + private List lbBackends; + private String lbRuleName; + private String lbCidrList; + private String reason; + + public NetrisNetworkRule(Builder builder) { + this.baseRule = builder.baseRule; + this.aclAction = builder.aclAction; + this.lbBackends = builder.lbBackends; + this.reason = builder.reason; + this.lbCidrList = builder.lbCidrList; + this.lbRuleName = builder.lbRuleName; + } + + public NetrisRuleAction getAclAction() { + return aclAction; + } + + public List getLbBackends() { + return lbBackends; + } + + public String getReason() { + return reason; + } + + public String getLbCidrList() {return lbCidrList; } + + public String getLbRuleName() { return lbRuleName; } + + public SDNProviderNetworkRule getBaseRule() { + return baseRule; + } + + // Builder class extending the parent builder + public static class Builder { + private SDNProviderNetworkRule baseRule; + private NetrisRuleAction aclAction; + private List lbBackends; + private String reason; + private String lbCidrList; + private String lbRuleName; + + public Builder baseRule(SDNProviderNetworkRule baseRule) { + this.baseRule = baseRule; + return this; + } + + public Builder aclAction(NetrisRuleAction aclAction) { + this.aclAction = aclAction; + return this; + } + + public Builder lbBackends(List lbBackends) { + this.lbBackends = lbBackends; + return this; + } + + public Builder reason(String reason) { + this.reason = reason; + return this; + } + + public Builder lbCidrList(String lbCidrList) { + this.lbCidrList = lbCidrList; + return this; + } + + public Builder lbRuleName(String lbRuleName) { + this.lbRuleName = lbRuleName; + return this; + } + + public NetrisNetworkRule build() { + return new NetrisNetworkRule(this); + } + } +} diff --git a/api/src/main/java/com/cloud/network/netris/NetrisProvider.java b/api/src/main/java/com/cloud/network/netris/NetrisProvider.java new file mode 100644 index 000000000000..fccf2930e976 --- /dev/null +++ b/api/src/main/java/com/cloud/network/netris/NetrisProvider.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.network.netris; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface NetrisProvider extends InternalIdentity, Identity { + long getZoneId(); + String getName(); + String getUrl(); + String getUsername(); + String getSiteName(); + String getTenantName(); + String getNetrisTag(); +} diff --git a/api/src/main/java/com/cloud/network/netris/NetrisService.java b/api/src/main/java/com/cloud/network/netris/NetrisService.java new file mode 100644 index 000000000000..110e9f07105a --- /dev/null +++ b/api/src/main/java/com/cloud/network/netris/NetrisService.java @@ -0,0 +1,310 @@ +// 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.network.netris; + +import com.cloud.network.IpAddress; +import com.cloud.network.Network; +import com.cloud.network.SDNProviderNetworkRule; +import com.cloud.network.vpc.StaticRoute; +import com.cloud.network.vpc.Vpc; + +import java.util.List; + +/** + * Interface for Netris Services that provides methods to manage VPCs, networks, + * NAT rules, network rules, and static routes in an SDN (Software Defined Networking) environment. + */ + +public interface NetrisService { + + /** + * Creates IPAM (IP Address Management) allocations for zone-level public ranges. + * + * @param zoneId the ID of the zone + * @return true if the operation is successful, false otherwise + */ + boolean createIPAMAllocationsForZoneLevelPublicRanges(long zoneId); + + /** + * Creates a VPC (Virtual Private Cloud) resource. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcId the ID of the VPC + * @param vpcName the name of the VPC + * @param sourceNatEnabled true if source NAT is enabled + * @param cidr the CIDR of the VPC + * @param isVpcNetwork true if it is a VPC network + * @return true if the operation is successful, false otherwise + */ + boolean createVpcResource(long zoneId, long accountId, long domainId, Long vpcId, String vpcName, boolean sourceNatEnabled, String cidr, boolean isVpcNetwork); + + /** + * Updates an existing VPC resource. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcId the ID of the VPC + * @param vpcName the new name of the VPC + * @param previousVpcName the previous name of the VPC + * @return true if the operation is successful, false otherwise + */ + boolean updateVpcResource(long zoneId, long accountId, long domainId, Long vpcId, String vpcName, String previousVpcName); + + /** + * Deletes a VPC resource. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpc the VPC to delete + * @return true if the operation is successful, false otherwise + */ + boolean deleteVpcResource(long zoneId, long accountId, long domainId, Vpc vpc); + + /** + * Creates a virtual network (vNet) resource. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcName the name of the VPC + * @param vpcId the ID of the VPC + * @param networkName the name of the network + * @param networkId the ID of the network + * @param cidr the CIDR of the network + * @param globalRouting true if global routing is enabled + * @return true if the operation is successful, false otherwise + */ + boolean createVnetResource(Long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String cidr, Boolean globalRouting); + + /** + * Updates an existing vNet resource. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcName the name of the VPC + * @param vpcId the ID of the VPC + * @param networkName the new name of the network + * @param networkId the ID of the network + * @param prevNetworkName the previous name of the network + * @return true if the operation is successful, false otherwise + */ + boolean updateVnetResource(Long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String prevNetworkName); + + /** + * Deletes an existing vNet resource. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcName the name of the VPC + * @param vpcId the ID of the VPC + * @param networkName the name of the network + * @param networkId the ID of the network + * @param cidr the CIDR of the network + * @return true if the operation is successful, false otherwise + */ + boolean deleteVnetResource(long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, String cidr); + + /** + * Creates a source NAT rule for a VPC or network. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcName the name of the VPC + * @param vpcId the ID of the VPC + * @param networkName the name of the network + * @param networkId the ID of the network + * @param isForVpc true if the rule applies to a VPC + * @param vpcCidr the VPC CIDR + * @param sourceNatIp the source NAT IP + * @return true if the operation is successful, false otherwise + */ + boolean createSnatRule(long zoneId, long accountId, long domainId, String vpcName, long vpcId, String networkName, long networkId, boolean isForVpc, String vpcCidr, String sourceNatIp); + + /** + * Creates a port forwarding rule for a VPC or network. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcName the name of the VPC + * @param vpcId the ID of the VPC + * @param networkName the name of the network + * @param networkId the ID of the network + * @param isForVpc true if the rule applies to a VPC + * @param vpcCidr the VPC CIDR + * @param networkRule the network rule to forward + * @return true if the operation is successful, false otherwise + */ + boolean createPortForwardingRule(long zoneId, long accountId, long domainId, String vpcName, long vpcId, String networkName, Long networkId, boolean isForVpc, String vpcCidr, SDNProviderNetworkRule networkRule); + + /** + * Deletes a port forwarding rule for a VPC or network. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param vpcName the name of the VPC + * @param vpcId the ID of the VPC + * @param networkName the name of the network + * @param networkId the ID of the network + * @param isForVpc true if the rule applies to a VPC + * @param vpcCidr the VPC CIDR + * @param networkRule the network rule to remove + * @return true if the operation is successful, false otherwise + */ + boolean deletePortForwardingRule(long zoneId, long accountId, long domainId, String vpcName, Long vpcId, String networkName, Long networkId, boolean isForVpc, String vpcCidr, SDNProviderNetworkRule networkRule); + + /** + * Updates the source NAT IP for a specified VPC. + * + * @param vpc the VPC to updates + * @param address the new source NAT IP address + * @return true if the operation is successful, false otherwise + */ + boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address); + + /** + * Creates a static NAT rule for a specific VM. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param networkResourceName the name of the network resource + * @param networkResourceId the ID of the network resource + * @param isForVpc true if the rule applies to a VPC + * @param vpcCidr the VPC CIDR + * @param staticNatIp the static NAT IP + * @param vmIp the VM's IP address + * @param vmId the ID of the VM + * @return true if the operation is successful, false otherwise + */ + boolean createStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String vpcCidr, String staticNatIp, String vmIp, long vmId); + + /** + * Deletes a static NAT rule for a specific VM. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param networkResourceName the name of the network resource + * @param networkResourceId the ID of the network resource + * @param isForVpc true if the rule applies to a VPC + * @param staticNatIp the static NAT IP + * @param vmId the ID of the VM + * @return true if the operation is successful, false otherwise + */ + boolean deleteStaticNatRule(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String staticNatIp, long vmId); + + /** + * Adds firewall rules to a specific network. + * + * @param network the target network + * @param firewallRules the list of firewall rules to add + * @return true if the operation is successful, false otherwise + */ + boolean addFirewallRules(Network network, List firewallRules); + + /** + * Deletes firewall rules from a specific network. + * + * @param network the target network + * @param firewallRules the list of firewall rules to delete + * @return true if the operation is successful, false otherwise + */ + boolean deleteFirewallRules(Network network, List firewallRules); + + /** + * Adds or updates a static route for a specific network or VPC. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param networkResourceName the name of the network resource + * @param networkResourceId the ID of the network resource + * @param isForVpc true if it is for a VPC + * @param prefix the IP prefix of the route + * @param nextHop the next hop address + * @param routeId the ID of the route + * @param updateRoute true if the route should be updated + * @return true if the operation is successful, false otherwise + */ + boolean addOrUpdateStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId, boolean updateRoute); + + /** + * Deletes a specific static route for a network or VPC. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param networkResourceName the name of the network resource + * @param networkResourceId the ID of the network resource + * @param isForVpc true if it is for a VPC + * @param prefix the IP prefix of the route + * @param nextHop the next hop address + * @param routeId the ID of the route + * @return true if the operation is successful, false otherwise + */ + boolean deleteStaticRoute(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId); + + /** + * Lists static routes for a specific network or VPC. + * + * @param zoneId the ID of the zone + * @param accountId the ID of the account + * @param domainId the ID of the domain + * @param networkResourceName the name of the network resource + * @param networkResourceId the ID of the network resource + * @param isForVpc true if it is for a VPC + * @param prefix the IP prefix of the route + * @param nextHop the next hop address + * @param routeId the ID of the route + * @return a list of static routes + */ + List listStaticRoutes(long zoneId, long accountId, long domainId, String networkResourceName, Long networkResourceId, boolean isForVpc, String prefix, String nextHop, Long routeId); + + /** + * Releases a NAT IP address. + * + * @param zoneId the ID of the zone + * @param publicIp the public NAT IP to release + * @return true if the operation is successful, false otherwise + */ + boolean releaseNatIp(long zoneId, String publicIp); + + /** + * Creates or updates a load balancer (LB) rule. + * + * @param rule the network rule for the load balancer + * @return true if the operation is successful, false otherwise + */ + boolean createOrUpdateLbRule(NetrisNetworkRule rule); + + /** + * Deletes a load balancer (LB) rule. + * + * @param rule the network rule to delete + * @return true if the operation is successful, false otherwise + */ + boolean deleteLbRule(NetrisNetworkRule rule); +} diff --git a/api/src/main/java/com/cloud/network/nsx/NsxService.java b/api/src/main/java/com/cloud/network/nsx/NsxService.java index bc4e6aafbfec..1adb7461cc09 100644 --- a/api/src/main/java/com/cloud/network/nsx/NsxService.java +++ b/api/src/main/java/com/cloud/network/nsx/NsxService.java @@ -16,9 +16,10 @@ // under the License. package com.cloud.network.nsx; +import org.apache.cloudstack.framework.config.ConfigKey; + import com.cloud.network.IpAddress; import com.cloud.network.vpc.Vpc; -import org.apache.cloudstack.framework.config.ConfigKey; public interface NsxService { @@ -33,4 +34,5 @@ public interface NsxService { boolean createVpcNetwork(Long zoneId, long accountId, long domainId, Long vpcId, String vpcName, boolean sourceNatEnabled); boolean updateVpcSourceNatIp(Vpc vpc, IpAddress address); + String getSegmentId(long domainId, long accountId, long zoneId, Long vpcId, long networkId); } diff --git a/api/src/main/java/com/cloud/network/rules/FirewallRule.java b/api/src/main/java/com/cloud/network/rules/FirewallRule.java index 369c6aa57eb8..38ba009163ce 100644 --- a/api/src/main/java/com/cloud/network/rules/FirewallRule.java +++ b/api/src/main/java/com/cloud/network/rules/FirewallRule.java @@ -69,7 +69,9 @@ enum TrafficType { State getState(); - long getNetworkId(); + Long getNetworkId(); + + Long getVpcId(); Long getSourceIpAddressId(); diff --git a/api/src/main/java/com/cloud/network/rules/LbStickinessMethod.java b/api/src/main/java/com/cloud/network/rules/LbStickinessMethod.java index 56a0622a52ba..5143611ee828 100644 --- a/api/src/main/java/com/cloud/network/rules/LbStickinessMethod.java +++ b/api/src/main/java/com/cloud/network/rules/LbStickinessMethod.java @@ -108,8 +108,7 @@ public LbStickinessMethod(StickinessMethodType methodType, String description) { } public void addParam(String name, Boolean required, String description, Boolean isFlag) { - /* FIXME : UI is breaking if the capability string length is larger , temporarily description is commented out */ - // LbStickinessMethodParam param = new LbStickinessMethodParam(name, required, description); + /* is this still a valid comment: FIXME : UI is breaking if the capability string length is larger , temporarily description is commented out */ LbStickinessMethodParam param = new LbStickinessMethodParam(name, required, " ", isFlag); _paramList.add(param); return; @@ -133,7 +132,6 @@ public String getDescription() { public void setDescription(String description) { /* FIXME : UI is breaking if the capability string length is larger , temporarily description is commented out */ - //this.description = description; this._description = " "; } } diff --git a/api/src/main/java/com/cloud/network/vpc/NetworkACLService.java b/api/src/main/java/com/cloud/network/vpc/NetworkACLService.java index 40aee1f08f1d..84e48d5d5b8a 100644 --- a/api/src/main/java/com/cloud/network/vpc/NetworkACLService.java +++ b/api/src/main/java/com/cloud/network/vpc/NetworkACLService.java @@ -19,6 +19,7 @@ import java.util.List; import org.apache.cloudstack.api.command.user.network.CreateNetworkACLCmd; +import org.apache.cloudstack.api.command.user.network.ImportNetworkACLCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkACLListsCmd; import org.apache.cloudstack.api.command.user.network.ListNetworkACLsCmd; import org.apache.cloudstack.api.command.user.network.MoveNetworkAclItemCmd; @@ -98,4 +99,6 @@ public interface NetworkACLService { NetworkACLItem moveNetworkAclRuleToNewPosition(MoveNetworkAclItemCmd moveNetworkAclItemCmd); NetworkACLItem moveRuleToTheTopInACLList(NetworkACLItem ruleBeingMoved); + + List importNetworkACLRules(ImportNetworkACLCmd cmd) throws ResourceUnavailableException; } diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java index 5707ca140246..739fca328b8c 100644 --- a/api/src/main/java/com/cloud/network/vpc/StaticRoute.java +++ b/api/src/main/java/com/cloud/network/vpc/StaticRoute.java @@ -25,6 +25,7 @@ enum State { Staged, // route been created but has never got through network rule conflict detection. Routes in this state can not be sent to VPC virtual router. Add, // Add means the route has been created and has gone through network rule conflict detection. Active, // Route has been sent to the VPC router and reported to be active. + Update, Revoke, // Revoke means this route has been revoked. If this route has been sent to the VPC router, the route will be deleted from database. Deleting // rule has been revoked and is scheduled for deletion } @@ -32,7 +33,9 @@ enum State { /** * @return */ - long getVpcGatewayId(); + Long getVpcGatewayId(); + + String getNextHop(); /** * @return diff --git a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java index cb4849f1f7b2..c8fc073911fe 100644 --- a/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java +++ b/api/src/main/java/com/cloud/network/vpc/StaticRouteProfile.java @@ -23,7 +23,8 @@ public class StaticRouteProfile implements StaticRoute { private String targetCidr; private long accountId; private long domainId; - private long gatewayId; + private Long gatewayId; + private String nextHop; private StaticRoute.State state; private long vpcId; String vlanTag; @@ -46,6 +47,18 @@ public StaticRouteProfile(StaticRoute staticRoute, VpcGateway gateway) { ipAddress = gateway.getIp4Address(); } + public StaticRouteProfile(StaticRoute staticRoute) { + id = staticRoute.getId(); + uuid = staticRoute.getUuid(); + targetCidr = staticRoute.getCidr(); + accountId = staticRoute.getAccountId(); + domainId = staticRoute.getDomainId(); + gatewayId = staticRoute.getVpcGatewayId(); + state = staticRoute.getState(); + vpcId = staticRoute.getVpcId(); + gateway = staticRoute.getNextHop(); + } + @Override public long getAccountId() { return accountId; @@ -57,10 +70,15 @@ public long getDomainId() { } @Override - public long getVpcGatewayId() { + public Long getVpcGatewayId() { return gatewayId; } + @Override + public String getNextHop() { + return nextHop; + } + @Override public String getCidr() { return targetCidr; 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 e9a831c9d839..a0686e2bf7d0 100644 --- a/api/src/main/java/com/cloud/network/vpc/Vpc.java +++ b/api/src/main/java/com/cloud/network/vpc/Vpc.java @@ -105,4 +105,8 @@ public enum State { String getIp6Dns1(); String getIp6Dns2(); + + boolean useRouterIpAsResolver(); + + boolean getKeepMacAddressOnPublicNic(); } diff --git a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java index 38263f59667c..f84602232159 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcOffering.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcOffering.java @@ -32,6 +32,8 @@ public enum State { public static final String redundantVPCOfferingName = "Redundant VPC offering"; public static final String DEFAULT_VPC_NAT_NSX_OFFERING_NAME = "VPC offering with NSX - NAT Mode"; public static final String DEFAULT_VPC_ROUTE_NSX_OFFERING_NAME = "VPC offering with NSX - Route Mode"; + public static final String DEFAULT_VPC_ROUTE_NETRIS_OFFERING_NAME = "VPC offering with Netris - Route Mode"; + public static final String DEFAULT_VPC_NAT_NETRIS_OFFERING_NAME = "VPC offering with Netris - NAT Mode"; /** * @@ -56,8 +58,6 @@ public enum State { */ boolean isDefault(); - boolean isForNsx(); - NetworkOffering.NetworkMode getNetworkMode(); /** @@ -84,4 +84,6 @@ public enum State { NetworkOffering.RoutingMode getRoutingMode(); Boolean isSpecifyAsNumber(); + + boolean isConserveMode(); } diff --git a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java index 10f1ddcc12d6..891cfb02d9df 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcProvisioningService.java @@ -20,6 +20,7 @@ import java.util.List; import java.util.Map; +import org.apache.cloudstack.api.command.admin.vpc.CloneVPCOfferingCmd; import org.apache.cloudstack.api.command.admin.vpc.CreateVPCOfferingCmd; import org.apache.cloudstack.api.command.admin.vpc.UpdateVPCOfferingCmd; import org.apache.cloudstack.api.command.user.vpc.ListVPCOfferingsCmd; @@ -34,12 +35,14 @@ public interface VpcProvisioningService { VpcOffering createVpcOffering(CreateVPCOfferingCmd cmd); + VpcOffering cloneVPCOffering(CloneVPCOfferingCmd cmd); + VpcOffering createVpcOffering(String name, String displayText, List supportedServices, Map> serviceProviders, Map serviceCapabilitystList, NetUtils.InternetProtocol internetProtocol, - Long serviceOfferingId, Boolean forNsx, NetworkOffering.NetworkMode networkMode, + Long serviceOfferingId, String externalProvider, NetworkOffering.NetworkMode networkMode, List domainIds, List zoneIds, VpcOffering.State state, - NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber); + NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber, boolean conserveMode); Pair,Integer> listVpcOfferings(ListVPCOfferingsCmd cmd); 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 af2a9847a62d..3d0ba43263f5 100644 --- a/api/src/main/java/com/cloud/network/vpc/VpcService.java +++ b/api/src/main/java/com/cloud/network/vpc/VpcService.java @@ -48,17 +48,17 @@ public interface VpcService { * @param vpcName * @param displayText * @param cidr - * @param networkDomain TODO + * @param networkDomain TODO * @param ip4Dns1 * @param ip4Dns2 - * @param displayVpc TODO + * @param displayVpc TODO + * @param useVrIpResolver * @return * @throws ResourceAllocationException TODO */ 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) - 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 @@ -238,7 +238,7 @@ Pair, Integer> listVpcs(Long id, String vpcName, String disp * @param cidr * @return */ - StaticRoute createStaticRoute(long gatewayId, String cidr) throws NetworkRuleConflictException; + StaticRoute createStaticRoute(Long gatewayId, Long vpcId, String nextHop, String cidr) throws NetworkRuleConflictException; /** * Lists static routes based on parameters passed to the call diff --git a/api/src/main/java/com/cloud/offering/DiskOffering.java b/api/src/main/java/com/cloud/offering/DiskOffering.java index e1c41f77cbf5..d74f5703cc99 100644 --- a/api/src/main/java/com/cloud/offering/DiskOffering.java +++ b/api/src/main/java/com/cloud/offering/DiskOffering.java @@ -37,7 +37,7 @@ enum State { State getState(); enum DiskCacheMode { - NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"); + NONE("none"), WRITEBACK("writeback"), WRITETHROUGH("writethrough"), HYPERVISOR_DEFAULT("hypervisor_default"); private final String _diskCacheMode; @@ -69,6 +69,8 @@ public String toString() { boolean isCustomized(); + boolean isShared(); + void setDiskSize(long diskSize); long getDiskSize(); @@ -99,7 +101,6 @@ public String toString() { Long getBytesReadRateMaxLength(); - void setBytesWriteRate(Long bytesWriteRate); Long getBytesWriteRate(); @@ -112,7 +113,6 @@ public String toString() { Long getBytesWriteRateMaxLength(); - void setIopsReadRate(Long iopsReadRate); Long getIopsReadRate(); @@ -133,7 +133,6 @@ public String toString() { Long getIopsWriteRateMax(); - void setIopsWriteRateMaxLength(Long iopsWriteRateMaxLength); Long getIopsWriteRateMaxLength(); diff --git a/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java b/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java index d83039e15c2b..197565a1fccb 100644 --- a/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java +++ b/api/src/main/java/com/cloud/offering/DiskOfferingInfo.java @@ -23,6 +23,7 @@ public class DiskOfferingInfo { private Long _size; private Long _minIops; private Long _maxIops; + private Long _kmsKeyId; public DiskOfferingInfo() { } @@ -31,6 +32,21 @@ public DiskOfferingInfo(DiskOffering diskOffering) { _diskOffering = diskOffering; } + public DiskOfferingInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops) { + _diskOffering = diskOffering; + _size = size; + _minIops = minIops; + _maxIops = maxIops; + } + + public DiskOfferingInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long kmsKeyId) { + _diskOffering = diskOffering; + _size = size; + _minIops = minIops; + _maxIops = maxIops; + _kmsKeyId = kmsKeyId; + } + public void setDiskOffering(DiskOffering diskOffering) { _diskOffering = diskOffering; } @@ -62,4 +78,12 @@ public void setMaxIops(Long maxIops) { public Long getMaxIops() { return _maxIops; } + + public void setKmsKeyId(Long kmsKeyId) { + _kmsKeyId = kmsKeyId; + } + + public Long getKmsKeyId() { + return _kmsKeyId; + } } diff --git a/api/src/main/java/com/cloud/offering/NetworkOffering.java b/api/src/main/java/com/cloud/offering/NetworkOffering.java index 7011aea679ee..5000a4f8c626 100644 --- a/api/src/main/java/com/cloud/offering/NetworkOffering.java +++ b/api/src/main/java/com/cloud/offering/NetworkOffering.java @@ -64,6 +64,8 @@ enum RoutingMode { public static final String DEFAULT_NAT_NSX_OFFERING_FOR_VPC = "DefaultNATNSXNetworkOfferingForVpc"; public static final String DEFAULT_NAT_NSX_OFFERING_FOR_VPC_WITH_ILB = "DefaultNATNSXNetworkOfferingForVpcWithInternalLB"; public static final String DEFAULT_ROUTED_NSX_OFFERING_FOR_VPC = "DefaultRoutedNSXNetworkOfferingForVpc"; + public static final String DEFAULT_ROUTED_NETRIS_OFFERING_FOR_VPC = "DefaultRoutedNetrisNetworkOfferingForVpc"; + public static final String DEFAULT_NAT_NETRIS_OFFERING_FOR_VPC = "DefaultNATNetrisNetworkOfferingForVpc"; public static final String DEFAULT_NAT_NSX_OFFERING = "DefaultNATNSXNetworkOffering"; public static final String DEFAULT_ROUTED_NSX_OFFERING = "DefaultRoutedNSXNetworkOffering"; public final static String QuickCloudNoServices = "QuickCloudNoServices"; @@ -102,10 +104,6 @@ enum RoutingMode { boolean isForVpc(); - boolean isForTungsten(); - - boolean isForNsx(); - NetworkMode getNetworkMode(); TrafficType getTrafficType(); diff --git a/api/src/main/java/com/cloud/offering/ServiceOffering.java b/api/src/main/java/com/cloud/offering/ServiceOffering.java index acb7a9f1cf91..532123e4373a 100644 --- a/api/src/main/java/com/cloud/offering/ServiceOffering.java +++ b/api/src/main/java/com/cloud/offering/ServiceOffering.java @@ -142,4 +142,8 @@ enum StorageType { Boolean getDiskOfferingStrictness(); void setDiskOfferingStrictness(boolean diskOfferingStrictness); + + Long getVgpuProfileId(); + + Integer getGpuCount(); } diff --git a/api/src/main/java/com/cloud/org/Cluster.java b/api/src/main/java/com/cloud/org/Cluster.java index 5124168084c6..b0aa6bb04cf2 100644 --- a/api/src/main/java/com/cloud/org/Cluster.java +++ b/api/src/main/java/com/cloud/org/Cluster.java @@ -41,4 +41,6 @@ public static enum ClusterType { ManagedState getManagedState(); CPU.CPUArch getArch(); + + String getStorageAccessGroups(); } diff --git a/api/src/main/java/com/cloud/resource/ResourceService.java b/api/src/main/java/com/cloud/resource/ResourceService.java index 2757c918ed65..3cdf8fc64e99 100644 --- a/api/src/main/java/com/cloud/resource/ResourceService.java +++ b/api/src/main/java/com/cloud/resource/ResourceService.java @@ -23,11 +23,11 @@ import org.apache.cloudstack.api.command.admin.cluster.UpdateClusterCmd; import org.apache.cloudstack.api.command.admin.host.AddHostCmd; import org.apache.cloudstack.api.command.admin.host.AddSecondaryStorageCmd; -import org.apache.cloudstack.api.command.admin.host.CancelMaintenanceCmd; +import org.apache.cloudstack.api.command.admin.host.CancelHostMaintenanceCmd; import org.apache.cloudstack.api.command.admin.host.ReconnectHostCmd; import org.apache.cloudstack.api.command.admin.host.UpdateHostCmd; import org.apache.cloudstack.api.command.admin.host.UpdateHostPasswordCmd; -import org.apache.cloudstack.api.command.admin.host.PrepareForMaintenanceCmd; +import org.apache.cloudstack.api.command.admin.host.PrepareForHostMaintenanceCmd; import org.apache.cloudstack.api.command.admin.host.DeclareHostAsDegradedCmd; import org.apache.cloudstack.api.command.admin.host.CancelHostAsDegradedCmd; @@ -51,7 +51,7 @@ public interface ResourceService { Host autoUpdateHostAllocationState(Long hostId, ResourceState.Event resourceEvent) throws NoTransitionException; - Host cancelMaintenance(CancelMaintenanceCmd cmd); + Host cancelMaintenance(CancelHostMaintenanceCmd cmd); Host reconnectHost(ReconnectHostCmd cmd) throws AgentUnavailableException; @@ -69,7 +69,7 @@ public interface ResourceService { List discoverHosts(AddSecondaryStorageCmd cmd) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException; - Host maintain(PrepareForMaintenanceCmd cmd); + Host maintain(PrepareForHostMaintenanceCmd cmd); Host declareHostAsDegraded(DeclareHostAsDegradedCmd cmd) throws NoTransitionException; @@ -95,4 +95,11 @@ public interface ResourceService { boolean releaseHostReservation(Long hostId); + void updatePodStorageAccessGroups(long podId, List newStorageAccessGroups); + + void updateZoneStorageAccessGroups(long zoneId, List newStorageAccessGroups); + + void updateClusterStorageAccessGroups(Long clusterId, List newStorageAccessGroups); + + void updateHostStorageAccessGroups(Long hostId, List newStorageAccessGroups); } diff --git a/api/src/main/java/com/cloud/resource/ResourceState.java b/api/src/main/java/com/cloud/resource/ResourceState.java index 70738c7921bc..e91cf820b081 100644 --- a/api/src/main/java/com/cloud/resource/ResourceState.java +++ b/api/src/main/java/com/cloud/resource/ResourceState.java @@ -76,6 +76,10 @@ public static Event toEvent(String e) { } } + public static List s_maintenanceStates = List.of(ResourceState.Maintenance, + ResourceState.ErrorInMaintenance, ResourceState.PrepareForMaintenance, + ResourceState.ErrorInPrepareForMaintenance); + public ResourceState getNextState(Event a) { return s_fsm.getNextState(this, a); } @@ -98,8 +102,7 @@ public static String[] toString(ResourceState... states) { } public static boolean isMaintenanceState(ResourceState state) { - return Arrays.asList(ResourceState.Maintenance, ResourceState.ErrorInMaintenance, - ResourceState.PrepareForMaintenance, ResourceState.ErrorInPrepareForMaintenance).contains(state); + return s_maintenanceStates.contains(state); } public static boolean canAttemptMaintenance(ResourceState state) { diff --git a/api/src/main/java/com/cloud/server/ManagementServerHostStats.java b/api/src/main/java/com/cloud/server/ManagementServerHostStats.java index 1eea7addba38..6eb275031e80 100644 --- a/api/src/main/java/com/cloud/server/ManagementServerHostStats.java +++ b/api/src/main/java/com/cloud/server/ManagementServerHostStats.java @@ -19,6 +19,7 @@ package com.cloud.server; import java.util.Date; +import java.util.List; /** * management server related stats @@ -70,6 +71,10 @@ public interface ManagementServerHostStats { String getOsDistribution(); + List getLastAgents(); + + List getAgents(); + int getAgentCount(); long getHeapMemoryUsed(); diff --git a/api/src/main/java/com/cloud/server/ManagementService.java b/api/src/main/java/com/cloud/server/ManagementService.java index 2670b657df13..bcca229c06bc 100644 --- a/api/src/main/java/com/cloud/server/ManagementService.java +++ b/api/src/main/java/com/cloud/server/ManagementService.java @@ -24,16 +24,20 @@ import org.apache.cloudstack.api.command.admin.config.ListCfgGroupsByCmd; import org.apache.cloudstack.api.command.admin.config.ListCfgsByCmd; import org.apache.cloudstack.api.command.admin.config.UpdateHypervisorCapabilitiesCmd; +import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.AddGuestOsMappingCmd; +import org.apache.cloudstack.api.command.admin.guest.DeleteGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.GetHypervisorGuestOsNamesCmd; import org.apache.cloudstack.api.command.admin.guest.ListGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.RemoveGuestOsMappingCmd; +import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCategoryCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsCmd; import org.apache.cloudstack.api.command.admin.guest.UpdateGuestOsMappingCmd; import org.apache.cloudstack.api.command.admin.host.ListHostsCmd; import org.apache.cloudstack.api.command.admin.host.UpdateHostPasswordCmd; +import org.apache.cloudstack.api.command.admin.management.RemoveManagementServerCmd; import org.apache.cloudstack.api.command.admin.pod.ListPodsByCmd; import org.apache.cloudstack.api.command.admin.resource.ArchiveAlertsCmd; import org.apache.cloudstack.api.command.admin.resource.DeleteAlertsCmd; @@ -58,14 +62,15 @@ import org.apache.cloudstack.api.command.user.ssh.DeleteSSHKeyPairCmd; import org.apache.cloudstack.api.command.user.ssh.ListSSHKeyPairsCmd; import org.apache.cloudstack.api.command.user.ssh.RegisterSSHKeyPairCmd; +import org.apache.cloudstack.api.command.user.userdata.DeleteCniConfigurationCmd; import org.apache.cloudstack.api.command.user.userdata.DeleteUserDataCmd; import org.apache.cloudstack.api.command.user.userdata.ListUserDataCmd; +import org.apache.cloudstack.api.command.user.userdata.RegisterCniConfigurationCmd; import org.apache.cloudstack.api.command.user.userdata.RegisterUserDataCmd; import org.apache.cloudstack.api.command.user.vm.GetVMPasswordCmd; import org.apache.cloudstack.api.command.user.vmgroup.UpdateVMGroupCmd; import org.apache.cloudstack.config.Configuration; import org.apache.cloudstack.config.ConfigurationGroup; -import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.alert.Alert; import com.cloud.capacity.Capacity; @@ -102,14 +107,6 @@ public interface ManagementService { static final String Name = "management-server"; - ConfigKey JsInterpretationEnabled = new ConfigKey<>("Hidden" - , Boolean.class - , "js.interpretation.enabled" - , "false" - , "Enable/Disable all JavaScript interpretation related functionalities to create or update Javascript rules." - , false - , ConfigKey.Scope.Global); - /** * returns the a map of the names/values in the configuration table * @@ -180,6 +177,12 @@ public interface ManagementService { */ Pair, Integer> listGuestOSCategoriesByCriteria(ListGuestOsCategoriesCmd cmd); + GuestOsCategory addGuestOsCategory(AddGuestOsCategoryCmd cmd); + + GuestOsCategory updateGuestOsCategory(UpdateGuestOsCategoryCmd cmd); + + boolean deleteGuestOsCategory(DeleteGuestOsCategoryCmd cmd); + /** * Obtains a list of all guest OS mappings * @@ -372,17 +375,23 @@ public interface ManagementService { * The api command class. * @return The list of userdatas found. */ - Pair, Integer> listUserDatas(ListUserDataCmd cmd); + Pair, Integer> listUserDatas(ListUserDataCmd cmd, boolean forCks); + + /** + * Registers a cni configuration. + * + * @param cmd The api command class. + * @return A VO with the registered user data. + */ + UserData registerCniConfiguration(RegisterCniConfigurationCmd cmd); /** * Registers a userdata. * - * @param cmd - * The api command class. + * @param cmd The api command class. * @return A VO with the registered userdata. */ UserData registerUserData(RegisterUserDataCmd cmd); - /** * Deletes a userdata. * @@ -392,6 +401,14 @@ public interface ManagementService { */ boolean deleteUserData(DeleteUserDataCmd cmd); + /** + * Deletes user data. + * + * @param cmd + * The api command class. + * @return True on success. False otherwise. + */ + boolean deleteCniConfiguration(DeleteCniConfigurationCmd cmd); /** * Search registered key pairs for the logged in user. * @@ -506,6 +523,6 @@ VirtualMachine upgradeSystemVM(ScaleSystemVMCmd cmd) throws ResourceUnavailableE Pair patchSystemVM(PatchSystemVMCmd cmd); - void checkJsInterpretationAllowedIfNeededForParameterValue(String paramName, boolean paramValue); + boolean removeManagementServer(RemoveManagementServerCmd cmd); } diff --git a/api/src/main/java/com/cloud/server/ResourceIconManager.java b/api/src/main/java/com/cloud/server/ResourceIconManager.java index e5111d9160b8..d10b3eb0cd5b 100644 --- a/api/src/main/java/com/cloud/server/ResourceIconManager.java +++ b/api/src/main/java/com/cloud/server/ResourceIconManager.java @@ -16,7 +16,9 @@ // under the License. package com.cloud.server; +import java.util.Collection; import java.util.List; +import java.util.Map; public interface ResourceIconManager { @@ -25,4 +27,8 @@ public interface ResourceIconManager { boolean deleteResourceIcon(List resourceIds, ResourceTag.ResourceObjectType resourceType); ResourceIcon getByResourceTypeAndUuid(ResourceTag.ResourceObjectType type, String resourceId); + + Map getByResourceTypeAndIds(ResourceTag.ResourceObjectType type, Collection resourceIds); + + Map getByResourceTypeAndUuids(ResourceTag.ResourceObjectType type, Collection resourceUuids); } diff --git a/api/src/main/java/com/cloud/server/ResourceTag.java b/api/src/main/java/com/cloud/server/ResourceTag.java index 9bbb5d43eaeb..32305753f1ae 100644 --- a/api/src/main/java/com/cloud/server/ResourceTag.java +++ b/api/src/main/java/com/cloud/server/ResourceTag.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.server; -import org.apache.cloudstack.acl.ControlledEntity; -import org.apache.cloudstack.api.Identity; -import org.apache.cloudstack.api.InternalIdentity; - import java.util.HashMap; import java.util.Locale; import java.util.Map; +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + public interface ResourceTag extends ControlledEntity, Identity, InternalIdentity { // FIXME - extract enum to another interface as its used both by resourceTags and resourceMetaData code @@ -66,10 +66,11 @@ public enum ResourceObjectType { LBStickinessPolicy(false, true), LBHealthCheckPolicy(false, true), SnapshotPolicy(true, true), + GuestOsCategory(false, false, true), GuestOs(false, true), NetworkOffering(false, true), VpcOffering(true, false), - Domain(false, false, true), + Domain(true, false, true), ObjectStore(false, false, true); diff --git a/api/src/main/java/com/cloud/storage/GuestOsCategory.java b/api/src/main/java/com/cloud/storage/GuestOsCategory.java index b46418d5c8f9..e1ee44891582 100644 --- a/api/src/main/java/com/cloud/storage/GuestOsCategory.java +++ b/api/src/main/java/com/cloud/storage/GuestOsCategory.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.storage; +import java.util.Date; + import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; @@ -27,4 +29,7 @@ public interface GuestOsCategory extends Identity, InternalIdentity { void setName(String name); + boolean isFeatured(); + + Date getCreated(); } diff --git a/api/src/main/java/com/cloud/storage/Snapshot.java b/api/src/main/java/com/cloud/storage/Snapshot.java index fc919e442b2e..c0a7b812ed9e 100644 --- a/api/src/main/java/com/cloud/storage/Snapshot.java +++ b/api/src/main/java/com/cloud/storage/Snapshot.java @@ -48,7 +48,7 @@ public boolean equals(String snapshotType) { } public enum State { - Allocated, Creating, CreatedOnPrimary, BackingUp, BackedUp, Copying, Destroying, Destroyed, + Allocated, Creating, CreatedOnPrimary, BackingUp, BackedUp, Copying, Destroying, Destroyed, Hidden, //it's a state, user can't see the snapshot from ui, while the snapshot may still exist on the storage Error; diff --git a/api/src/main/java/com/cloud/storage/Storage.java b/api/src/main/java/com/cloud/storage/Storage.java index 05b8b3ab7a86..3511b4e88cb9 100644 --- a/api/src/main/java/com/cloud/storage/Storage.java +++ b/api/src/main/java/com/cloud/storage/Storage.java @@ -30,11 +30,13 @@ public static enum ImageFormat { OVA(true, true, true, "ova"), VHDX(true, true, true, "vhdx"), BAREMETAL(false, false, false, "BAREMETAL"), + EXTERNAL(false, false, false, "EXTERNAL"), VMDK(true, true, false, "vmdk"), VDI(true, true, false, "vdi"), TAR(false, false, false, "tar"), ZIP(false, false, false, "zip"), - DIR(false, false, false, "dir"); + DIR(false, false, false, "dir"), + PNG(false, false, false, "png"); private final boolean supportThinProvisioning; private final boolean supportSparse; @@ -127,7 +129,7 @@ public static enum FileSystem { public static enum TemplateType { ROUTING, // Router template SYSTEM, /* routing, system vm template */ - BUILTIN, /* buildin template */ + BUILTIN, /* builtin template */ PERHOST, /* every host has this template, don't need to install it in secondary storage */ USER, /* User supplied template/iso */ VNF, /* VNFs (virtual network functions) template */ @@ -169,6 +171,7 @@ public static enum StoragePoolType { ISO(false, false, EncryptionSupport.Unsupported), // for iso image LVM(false, false, EncryptionSupport.Unsupported), // XenServer local LVM SR CLVM(true, false, EncryptionSupport.Unsupported), + CLVM_NG(true, false, EncryptionSupport.Hypervisor), RBD(true, true, EncryptionSupport.Unsupported), // http://libvirt.org/storage.html#StorageBackendRBD SharedMountPoint(true, true, EncryptionSupport.Hypervisor), VMFS(true, true, EncryptionSupport.Unsupported), // VMware VMFS storage diff --git a/api/src/main/java/com/cloud/storage/StorageService.java b/api/src/main/java/com/cloud/storage/StorageService.java index b8df75cd3e4c..a29c8f6aecef 100644 --- a/api/src/main/java/com/cloud/storage/StorageService.java +++ b/api/src/main/java/com/cloud/storage/StorageService.java @@ -22,6 +22,7 @@ import org.apache.cloudstack.api.command.admin.storage.CancelPrimaryStorageMaintenanceCmd; import org.apache.cloudstack.api.command.admin.storage.ChangeStoragePoolScopeCmd; +import org.apache.cloudstack.api.command.admin.storage.ConfigureStorageAccessCmd; import org.apache.cloudstack.api.command.admin.storage.CreateSecondaryStagingStoreCmd; import org.apache.cloudstack.api.command.admin.storage.CreateStoragePoolCmd; import org.apache.cloudstack.api.command.admin.storage.DeleteImageStoreCmd; @@ -99,6 +100,8 @@ public interface StorageService { StoragePool disablePrimaryStoragePool(Long id); + boolean configureStorageAccess(ConfigureStorageAccessCmd cmd); + StoragePool getStoragePool(long id); boolean deleteImageStore(DeleteImageStoreCmd cmd); @@ -131,7 +134,7 @@ public interface StorageService { void removeSecondaryStorageHeuristic(RemoveSecondaryStorageSelectorCmd cmd); - ObjectStore discoverObjectStore(String name, String url, String providerName, Map details) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException; + ObjectStore discoverObjectStore(String name, String url, Long size, String providerName, Map details) throws IllegalArgumentException, DiscoveryException, InvalidParameterValueException; boolean deleteObjectStore(DeleteObjectStoragePoolCmd cmd); diff --git a/api/src/main/java/com/cloud/storage/Volume.java b/api/src/main/java/com/cloud/storage/Volume.java index c7fbdb0a5445..89298e04587f 100644 --- a/api/src/main/java/com/cloud/storage/Volume.java +++ b/api/src/main/java/com/cloud/storage/Volume.java @@ -60,7 +60,9 @@ enum State { UploadError(false, "Volume upload encountered some error"), UploadAbandoned(false, "Volume upload is abandoned since the upload was never initiated within a specified time"), Attaching(true, "The volume is attaching to a VM from Ready state."), - Restoring(true, "The volume is being restored from backup."); + Restoring(true, "The volume is being restored from backup."), + Consolidating(true, "The volume is being flattened."), + RestoreError(false, "The volume restore encountered an error."); boolean _transitional; @@ -153,6 +155,10 @@ public String getDescription() { s_fsm.addTransition(new StateMachine2.Transition(Destroy, Event.RestoreRequested, Restoring, null)); s_fsm.addTransition(new StateMachine2.Transition(Restoring, Event.RestoreSucceeded, Ready, null)); s_fsm.addTransition(new StateMachine2.Transition(Restoring, Event.RestoreFailed, Ready, null)); + s_fsm.addTransition(new StateMachine2.Transition<>(Ready, Event.ConsolidationRequested, Consolidating, null)); + s_fsm.addTransition(new StateMachine2.Transition<>(Consolidating, Event.OperationSucceeded, Ready, null)); + s_fsm.addTransition(new StateMachine2.Transition<>(Consolidating, Event.OperationFailed, RestoreError, null)); + s_fsm.addTransition(new StateMachine2.Transition<>(RestoreError, Event.RestoreFailed, RestoreError, null)); } } @@ -179,7 +185,8 @@ enum Event { OperationTimeout, RestoreRequested, RestoreSucceeded, - RestoreFailed; + RestoreFailed, + ConsolidationRequested } /** @@ -275,6 +282,14 @@ enum Event { void setPassphraseId(Long id); + Long getKmsKeyId(); + + void setKmsKeyId(Long id); + + Long getKmsWrappedKeyId(); + + void setKmsWrappedKeyId(Long id); + String getEncryptFormat(); void setEncryptFormat(String encryptFormat); diff --git a/api/src/main/java/com/cloud/storage/VolumeApiService.java b/api/src/main/java/com/cloud/storage/VolumeApiService.java index 4182728c204a..372eb0385618 100644 --- a/api/src/main/java/com/cloud/storage/VolumeApiService.java +++ b/api/src/main/java/com/cloud/storage/VolumeApiService.java @@ -22,6 +22,7 @@ import java.util.List; import java.util.Map; +import com.cloud.dc.DataCenter; import com.cloud.exception.ResourceAllocationException; import com.cloud.offering.DiskOffering; import com.cloud.user.Account; @@ -56,9 +57,9 @@ public interface VolumeApiService { Boolean.class, "use.https.to.upload", "true", - "Determines the protocol (HTTPS or HTTP) ACS will use to generate links to upload ISOs, volumes, and templates. When set as 'true', ACS will use protocol HTTPS, otherwise, it will use protocol HTTP. Default value is 'true'.", + "Controls whether upload links for ISOs, volumes, and templates use HTTPS (true, default) or HTTP (false). After changing this setting, the Secondary Storage VM (SSVM) must be recreated", true, - ConfigKey.Scope.StoragePool); + ConfigKey.Scope.Zone); /** * Creates the database object for a volume based on the given criteria @@ -70,6 +71,10 @@ public interface VolumeApiService { */ Volume allocVolume(CreateVolumeCmd cmd) throws ResourceAllocationException; + Volume allocVolume(long ownerId, Long zoneId, Long diskOfferingId, Long vmId, Long snapshotId, String name, + Long cmdSize, Boolean displayVolume, Long cmdMinIops, Long cmdMaxIops, String customId, Long kmsKeyId) + throws ResourceAllocationException; + /** * Creates the volume based on the given criteria * @@ -80,6 +85,8 @@ public interface VolumeApiService { */ Volume createVolume(CreateVolumeCmd cmd); + Volume createVolume(long volumeId, Long vmId, Long snapshotId, Long storageId, Boolean display); + /** * Resizes the volume based on the given criteria * @@ -107,16 +114,16 @@ public interface VolumeApiService { Volume attachVolumeToVM(AttachVolumeCmd command); - Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean allowAttachForSharedFS); + Volume attachVolumeToVM(Long vmId, Long volumeId, Long deviceId, Boolean allowAttachForSharedFS, boolean allowAttachOnRestoring); Volume detachVolumeViaDestroyVM(long vmId, long volumeId); Volume detachVolumeFromVM(DetachVolumeCmd cmd); - Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map tags, List zoneIds) + Snapshot takeSnapshot(Long volumeId, Long policyId, Long snapshotId, Account account, boolean quiescevm, Snapshot.LocationType locationType, boolean asyncBackup, Map tags, List zoneIds, List poolIds, Boolean useStorageReplication) throws ResourceAllocationException; - Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List zoneIds) throws ResourceAllocationException; + Snapshot allocSnapshot(Long volumeId, Long policyId, String snapshotName, Snapshot.LocationType locationType, List zoneIds, List storagePoolIds, Boolean useStorageReplication) throws ResourceAllocationException; Volume updateVolume(long volumeId, String path, String state, Long storageId, Boolean displayVolume, Boolean deleteProtection, @@ -137,7 +144,7 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId, void updateDisplay(Volume volume, Boolean displayVolume); - Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName) throws ResourceAllocationException; + Snapshot allocSnapshotForVm(Long vmId, Long volumeId, String snapshotName, Long vmSnapshotId) throws ResourceAllocationException; /** * Checks if the storage pool supports the disk offering tags. @@ -180,7 +187,9 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId, */ boolean doesStoragePoolSupportDiskOfferingTags(StoragePool destPool, String diskOfferingTags); - Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge); + boolean validateConditionsToReplaceDiskOfferingOfVolume(Volume volume, DiskOffering newDiskOffering, StoragePool destPool); + + Volume destroyVolume(long volumeId, Account caller, boolean expunge, boolean forceExpunge, Boolean countDisplayFalseInResourceCount); void destroyVolume(long volumeId); @@ -199,4 +208,8 @@ Volume updateVolume(long volumeId, String path, String state, Long storageId, boolean stateTransitTo(Volume vol, Volume.Event event) throws NoTransitionException; Pair checkAndRepairVolume(CheckAndRepairVolumeCmd cmd) throws ResourceAllocationException; + + Long getVolumePhysicalSize(Storage.ImageFormat format, String path, String chainInfo); + + Long getCustomDiskOfferingIdForVolumeUpload(Account owner, DataCenter zone, boolean encryptEnabledOnly); } diff --git a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java index 67afd6aa4e24..d52e645ec799 100644 --- a/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java +++ b/api/src/main/java/com/cloud/storage/snapshot/SnapshotApiService.java @@ -85,7 +85,7 @@ public interface SnapshotApiService { * the command that specifies the volume criteria * @return list of snapshot policies */ - Pair, Integer> listPoliciesforVolume(ListSnapshotPoliciesCmd cmd); + Pair, Integer> listSnapshotPolicies(ListSnapshotPoliciesCmd cmd); boolean deleteSnapshotPolicies(DeleteSnapshotPoliciesCmd cmd); diff --git a/api/src/main/java/com/cloud/storage/snapshot/SnapshotPolicy.java b/api/src/main/java/com/cloud/storage/snapshot/SnapshotPolicy.java index 22d5dfb9c1b8..13009a9808aa 100644 --- a/api/src/main/java/com/cloud/storage/snapshot/SnapshotPolicy.java +++ b/api/src/main/java/com/cloud/storage/snapshot/SnapshotPolicy.java @@ -16,11 +16,12 @@ // under the License. package com.cloud.storage.snapshot; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Displayable; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; -public interface SnapshotPolicy extends Identity, InternalIdentity, Displayable { +public interface SnapshotPolicy extends ControlledEntity, Identity, InternalIdentity, Displayable { long getVolumeId(); diff --git a/api/src/main/java/com/cloud/template/TemplateApiService.java b/api/src/main/java/com/cloud/template/TemplateApiService.java index 5b494c308c3c..6138f24c92b0 100644 --- a/api/src/main/java/com/cloud/template/TemplateApiService.java +++ b/api/src/main/java/com/cloud/template/TemplateApiService.java @@ -58,10 +58,23 @@ public interface TemplateApiService { VirtualMachineTemplate prepareTemplate(long templateId, long zoneId, Long storageId); + /** + * Detach ISO from VM + * @param vmId id of the VM + * @param isoId id of the ISO (when passed). If it is not passed, it will get it from user_vm table + * @param extraParams forced, isVirtualRouter + * @return true when operation succeeds, false if not + */ + boolean detachIso(long vmId, Long isoId, Boolean... extraParams); - boolean detachIso(long vmId, boolean forced); - - boolean attachIso(long isoId, long vmId, boolean forced); + /** + * Attach ISO to a VM + * @param isoId id of the ISO to attach + * @param vmId id of the VM to attach the ISO to + * @param extraParams: forced, isVirtualRouter + * @return true when operation succeeds, false if not + */ + boolean attachIso(long isoId, long vmId, Boolean... extraParams); /** * Deletes a template diff --git a/api/src/main/java/com/cloud/template/VirtualMachineTemplate.java b/api/src/main/java/com/cloud/template/VirtualMachineTemplate.java index d8872d5fe724..b8c646048b97 100644 --- a/api/src/main/java/com/cloud/template/VirtualMachineTemplate.java +++ b/api/src/main/java/com/cloud/template/VirtualMachineTemplate.java @@ -145,10 +145,14 @@ public enum TemplateFilter { boolean isDeployAsIs(); + boolean isForCks(); + Long getUserDataId(); UserData.UserDataOverridePolicy getUserDataOverridePolicy(); CPU.CPUArch getArch(); + Long getExtensionId(); + } diff --git a/api/src/main/java/com/cloud/user/AccountService.java b/api/src/main/java/com/cloud/user/AccountService.java index c0ebcf09f59b..fc450e9179c5 100644 --- a/api/src/main/java/com/cloud/user/AccountService.java +++ b/api/src/main/java/com/cloud/user/AccountService.java @@ -21,12 +21,13 @@ import com.cloud.utils.Pair; import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.acl.RolePermissionEntity; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; +import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.command.admin.account.CreateAccountCmd; -import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; -import org.apache.cloudstack.api.command.admin.user.RegisterCmd; -import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; import com.cloud.dc.DataCenter; import com.cloud.domain.Domain; @@ -35,7 +36,16 @@ import com.cloud.offering.DiskOffering; import com.cloud.offering.NetworkOffering; import com.cloud.offering.ServiceOffering; +import org.apache.cloudstack.api.command.admin.user.DeleteUserKeysCmd; +import org.apache.cloudstack.api.command.admin.user.GetUserKeysCmd; +import org.apache.cloudstack.api.command.admin.user.ListUserKeyRulesCmd; +import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd; +import org.apache.cloudstack.api.command.admin.user.RegisterUserKeysCmd; +import org.apache.cloudstack.api.command.admin.user.UpdateUserCmd; +import org.apache.cloudstack.api.response.ApiKeyPairResponse; +import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.auth.UserTwoFactorAuthenticator; +import org.apache.cloudstack.backup.BackupOffering; public interface AccountService { @@ -58,7 +68,8 @@ UserAccount createUserAccount(String userName, String password, String firstName User getSystemUser(); - User createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID); + User createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, + String accountName, Long domainId, String userUUID, boolean isPasswordChangeRequired); User createUser(String userName, String password, String firstName, String lastName, String email, String timeZone, String accountName, Long domainId, String userUUID, User.Source source); @@ -77,10 +88,16 @@ User createUser(String userName, String password, String firstName, String lastN Account getActiveAccountById(long accountId); + Account getActiveAccountByUuid(String accountUuid); + Account getAccount(long accountId); + Account getAccountByUuid(String accountUuid); + User getActiveUser(long userId); + User getOneActiveUserForAccount(Account account); + User getUserIncludingRemoved(long userId); boolean isRootAdmin(Long accountId); @@ -95,7 +112,7 @@ User createUser(String userName, String password, String firstName, String lastN void markUserRegistered(long userId); - public String[] createApiKeyAndSecretKey(RegisterCmd cmd); + ApiKeyPair createApiKeyAndSecretKey(RegisterUserKeysCmd cmd); public String[] createApiKeyAndSecretKey(final long userId); @@ -115,13 +132,19 @@ User createUser(String userName, String password, String firstName, String lastN void checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException; + void checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException; + void checkAccess(User user, ControlledEntity entity); void checkAccess(Account account, AccessType accessType, boolean sameOwner, String apiName, ControlledEntity... entities) throws PermissionDeniedException; void validateAccountHasAccessToResource(Account account, AccessType accessType, Object resource); - Long finalyzeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly); + void validateCallingUserHasAccessToDesiredUser(Long userId); + + Long finalizeAccountId(String accountName, Long domainId, Long projectId, boolean enabledOnly); + + Long finalizeAccountId(Long accountId, String accountName, Long domainId, Long projectId); /** * returns the user account object for a given user id @@ -130,9 +153,15 @@ User createUser(String userName, String password, String firstName, String lastN */ UserAccount getUserAccountById(Long userId); - public Pair> getKeys(GetUserKeysCmd cmd); + Pair> getKeys(GetUserKeysCmd cmd); + + ListResponse listKeys(ListUserKeysCmd cmd); + + List listKeyRules(ListUserKeyRulesCmd cmd); + + void deleteApiKey(DeleteUserKeysCmd cmd); - public Pair> getKeys(Long userId); + void deleteApiKey(ApiKeyPair id); /** * Lists user two-factor authentication provider plugins @@ -147,4 +176,13 @@ User createUser(String userName, String password, String firstName, String lastN */ UserTwoFactorAuthenticator getUserTwoFactorAuthenticationProvider(final Long domainId); + ApiKeyPair getLatestUserKeyPair(Long userId); + + ApiKeyPair getKeyPairById(Long id); + + ApiKeyPair getKeyPairByApiKey(String apiKey); + + String getAccessingApiKey(BaseCmd cmd); + + List getAllKeypairPermissions(String apiKey); } diff --git a/api/src/main/java/com/cloud/user/ApiKeyPairState.java b/api/src/main/java/com/cloud/user/ApiKeyPairState.java new file mode 100644 index 000000000000..63405c62e320 --- /dev/null +++ b/api/src/main/java/com/cloud/user/ApiKeyPairState.java @@ -0,0 +1,21 @@ +// 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.user; + +public enum ApiKeyPairState { + ENABLED, REMOVED, EXPIRED +} diff --git a/api/src/main/java/com/cloud/user/ResourceLimitService.java b/api/src/main/java/com/cloud/user/ResourceLimitService.java index d725c4a967ba..89128f87829e 100644 --- a/api/src/main/java/com/cloud/user/ResourceLimitService.java +++ b/api/src/main/java/com/cloud/user/ResourceLimitService.java @@ -51,8 +51,14 @@ public interface ResourceLimitService { "The default maximum number of projects that can be created for an account",false); static final ConfigKey DefaultMaxDomainProjects = new ConfigKey<>("Domain Defaults",Long.class,"max.domain.projects","50", "The default maximum number of projects that can be created for a domain",false); - - static final List HostTagsSupportingTypes = List.of(ResourceType.user_vm, ResourceType.cpu, ResourceType.memory); + static final ConfigKey DefaultMaxAccountGpus = new ConfigKey<>("Account Defaults",Long.class,"max.account.gpus","20", + "The default maximum number of GPU devices that can be used for an account", false); + static final ConfigKey DefaultMaxDomainGpus = new ConfigKey<>("Domain Defaults",Long.class,"max.domain.gpus","20", + "The default maximum number of GPU devices that can be used for a domain", false); + static final ConfigKey DefaultMaxProjectGpus = new ConfigKey<>("Project Defaults",Long.class,"max.project.gpus","20", + "The default maximum number of GPU devices that can be used for a project", false); + + static final List HostTagsSupportingTypes = List.of(ResourceType.user_vm, ResourceType.cpu, ResourceType.memory, ResourceType.gpu); static final List StorageTagsSupportingTypes = List.of(ResourceType.volume, ResourceType.primary_storage); /** @@ -248,14 +254,14 @@ public interface ResourceLimitService { void updateTaggedResourceLimitsAndCountsForAccounts(List responses, String tag); void updateTaggedResourceLimitsAndCountsForDomains(List responses, String tag); void checkVolumeResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException; - List getResourceLimitStorageTagsForResourceCountOperation(Boolean display, DiskOffering diskOffering); + List getResourceLimitStorageTagsForResourceCountOperation(Boolean display, DiskOffering diskOffering, Boolean enforceResourceLimitOnDisplayFalse); void checkVolumeResourceLimitForDiskOfferingChange(Account owner, Boolean display, Long currentSize, Long newSize, DiskOffering currentOffering, DiskOffering newOffering, List reservations) throws ResourceAllocationException; void checkPrimaryStorageResourceLimit(Account owner, Boolean display, Long size, DiskOffering diskOffering, List reservations) throws ResourceAllocationException; void incrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); - void decrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); + void decrementVolumeResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering, Boolean countDisplayFalseInResourceCount); void updateVmResourceCountForTemplateChange(long accountId, Boolean display, ServiceOffering offering, VirtualMachineTemplate currentTemplate, VirtualMachineTemplate newTemplate); @@ -270,8 +276,8 @@ void updateVolumeResourceCountForDiskOfferingChange(long accountId, Boolean disp void incrementVolumePrimaryStorageResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); void decrementVolumePrimaryStorageResourceCount(long accountId, Boolean display, Long size, DiskOffering diskOffering); void checkVmResourceLimit(Account owner, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException; - void incrementVmResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template); - void decrementVmResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template); + void incrementVmResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Boolean countDisplayFalseInResourceLimit); + void decrementVmResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Boolean countDisplayFalseInResourceCount); void checkVmResourceLimitsForServiceOfferingChange(Account owner, Boolean display, Long currentCpu, Long newCpu, Long currentMemory, Long newMemory, ServiceOffering currentOffering, ServiceOffering newOffering, VirtualMachineTemplate template, List reservations) throws ResourceAllocationException; @@ -284,5 +290,8 @@ void checkVmResourceLimitsForTemplateChange(Account owner, Boolean display, Serv void incrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory); void decrementVmMemoryResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long memory); + void incrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu); + void decrementVmGpuResourceCount(long accountId, Boolean display, ServiceOffering serviceOffering, VirtualMachineTemplate template, Long gpu); + long recalculateDomainResourceCount(final long domainId, final ResourceType type, String tag); } diff --git a/api/src/main/java/com/cloud/user/User.java b/api/src/main/java/com/cloud/user/User.java index 041b39ad2729..da7245a47980 100644 --- a/api/src/main/java/com/cloud/user/User.java +++ b/api/src/main/java/com/cloud/user/User.java @@ -65,14 +65,6 @@ public enum Source { public void setState(Account.State state); - public String getApiKey(); - - public void setApiKey(String apiKey); - - public String getSecretKey(); - - public void setSecretKey(String secretKey); - public String getTimezone(); public void setTimezone(String timezone); diff --git a/api/src/main/java/com/cloud/user/UserAccount.java b/api/src/main/java/com/cloud/user/UserAccount.java index e6b07fb371eb..5736244e3259 100644 --- a/api/src/main/java/com/cloud/user/UserAccount.java +++ b/api/src/main/java/com/cloud/user/UserAccount.java @@ -39,10 +39,6 @@ public interface UserAccount extends InternalIdentity { String getState(); - String getApiKey(); - - String getSecretKey(); - Date getCreated(); Date getRemoved(); diff --git a/api/src/main/java/com/cloud/user/UserData.java b/api/src/main/java/com/cloud/user/UserData.java index fa0c50473c0d..13a3c74f3679 100644 --- a/api/src/main/java/com/cloud/user/UserData.java +++ b/api/src/main/java/com/cloud/user/UserData.java @@ -29,4 +29,5 @@ public enum UserDataOverridePolicy { String getUserData(); String getParams(); + boolean isForCks(); } diff --git a/api/src/main/java/com/cloud/vm/DiskProfile.java b/api/src/main/java/com/cloud/vm/DiskProfile.java index 971ebde496e4..766573d4d40b 100644 --- a/api/src/main/java/com/cloud/vm/DiskProfile.java +++ b/api/src/main/java/com/cloud/vm/DiskProfile.java @@ -82,7 +82,7 @@ public DiskProfile(Volume vol, DiskOffering offering, HypervisorType hyperType) null); this.hyperType = hyperType; this.provisioningType = offering.getProvisioningType(); - this.requiresEncryption = offering.getEncrypt() || vol.getPassphraseId() != null; + this.requiresEncryption = offering.getEncrypt() || vol.getPassphraseId() != null || vol.getKmsKeyId() != null; } public DiskProfile(DiskProfile dp) { diff --git a/api/src/main/java/com/cloud/vm/Nic.java b/api/src/main/java/com/cloud/vm/Nic.java index afc44b8d39fa..3722e5769c92 100644 --- a/api/src/main/java/com/cloud/vm/Nic.java +++ b/api/src/main/java/com/cloud/vm/Nic.java @@ -33,6 +33,11 @@ * Nic represents one nic on the VM. */ public interface Nic extends Identity, InternalIdentity { + + interface Topics { + String NIC_LIFECYCLE = "nic.lifecycle"; + } + enum Event { ReservationRequested, ReleaseRequested, CancelRequested, OperationCompleted, OperationFailed, } @@ -162,4 +167,6 @@ public enum ReservationStrategy { String getIPv6Address(); Integer getMtu(); + + boolean isEnabled(); } diff --git a/api/src/main/java/com/cloud/vm/NicProfile.java b/api/src/main/java/com/cloud/vm/NicProfile.java index a0c80ceb1bfb..54a32bbcb181 100644 --- a/api/src/main/java/com/cloud/vm/NicProfile.java +++ b/api/src/main/java/com/cloud/vm/NicProfile.java @@ -52,6 +52,7 @@ public class NicProfile implements InternalIdentity, Serializable { boolean defaultNic; Integer networkRate; boolean isSecurityGroupEnabled; + boolean enabled; Integer orderIndex; @@ -87,6 +88,7 @@ public NicProfile(Nic nic, Network network, URI broadcastUri, URI isolationUri, broadcastType = network.getBroadcastDomainType(); trafficType = network.getTrafficType(); format = nic.getAddressFormat(); + enabled = nic.isEnabled(); iPv4Address = nic.getIPv4Address(); iPv4Netmask = nic.getIPv4Netmask(); @@ -414,6 +416,14 @@ public void setIpv4AllocationRaceCheck(boolean ipv4AllocationRaceCheck) { this.ipv4AllocationRaceCheck = ipv4AllocationRaceCheck; } + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + // // OTHER METHODS // @@ -453,6 +463,6 @@ public String toString() { return String.format("NicProfile %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( this, "id", "uuid", "vmId", "deviceId", - "broadcastUri", "reservationId", "iPv4Address")); + "broadcastType", "broadcastUri", "reservationId", "iPv4Address")); } } diff --git a/api/src/main/java/com/cloud/vm/NicSecondaryIp.java b/api/src/main/java/com/cloud/vm/NicSecondaryIp.java index 2856e0aea756..d25627c1782d 100644 --- a/api/src/main/java/com/cloud/vm/NicSecondaryIp.java +++ b/api/src/main/java/com/cloud/vm/NicSecondaryIp.java @@ -38,6 +38,8 @@ public interface NicSecondaryIp extends ControlledEntity, Identity, InternalIden String getIp6Address(); + String getDescription(); + long getNetworkId(); long getVmId(); diff --git a/api/src/main/java/com/cloud/vm/UserVmService.java b/api/src/main/java/com/cloud/vm/UserVmService.java index dc9e8c1f0d8c..5864e91cd7f4 100644 --- a/api/src/main/java/com/cloud/vm/UserVmService.java +++ b/api/src/main/java/com/cloud/vm/UserVmService.java @@ -16,14 +16,18 @@ // under the License. package com.cloud.vm; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import com.cloud.deploy.DeploymentPlan; import org.apache.cloudstack.api.BaseCmd.HTTPMethod; import org.apache.cloudstack.api.command.admin.vm.AssignVMCmd; import org.apache.cloudstack.api.command.admin.vm.RecoverVMCmd; import org.apache.cloudstack.api.command.user.vm.AddNicToVMCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; import org.apache.cloudstack.api.command.user.vm.RebootVMCmd; @@ -36,6 +40,7 @@ import org.apache.cloudstack.api.command.user.vm.StartVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateDefaultNicForVMCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVMCmd; +import org.apache.cloudstack.api.command.user.vm.UpdateVmNicCmd; import org.apache.cloudstack.api.command.user.vm.UpdateVmNicIpCmd; import org.apache.cloudstack.api.command.user.vm.UpgradeVMCmd; import org.apache.cloudstack.api.command.user.vmgroup.CreateVMGroupCmd; @@ -60,6 +65,7 @@ import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; import com.cloud.utils.exception.ExecutionException; public interface UserVmService { @@ -69,10 +75,11 @@ public interface UserVmService { * Destroys one virtual machine * * @param cmd the API Command Object containg the parameters to use for this service action + * @param checkExpunge * @throws ConcurrentOperationException * @throws ResourceUnavailableException */ - UserVm destroyVm(DestroyVMCmd cmd) throws ResourceUnavailableException, ConcurrentOperationException; + UserVm destroyVm(DestroyVMCmd cmd, boolean checkExpunge) throws ResourceUnavailableException, ConcurrentOperationException; /** * Destroys one virtual machine @@ -111,7 +118,7 @@ UserVm startVirtualMachine(StartVMCmd cmd) throws StorageUnavailableException, E UserVm rebootVirtualMachine(RebootVMCmd cmd) throws InsufficientCapacityException, ResourceUnavailableException, ResourceAllocationException; - void startVirtualMachine(UserVm vm) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException; + void startVirtualMachine(UserVm vm, DeploymentPlan plan) throws OperationTimedoutException, ResourceUnavailableException, InsufficientCapacityException; void startVirtualMachineForHA(VirtualMachine vm, Map params, DeploymentPlanner planner) throws InsufficientCapacityException, ResourceUnavailableException, @@ -147,6 +154,8 @@ void startVirtualMachineForHA(VirtualMachine vm, Map securityGroupIdList, - Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, + Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskInfoList, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIp, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameter, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId) throws InsufficientCapacityException, + Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** @@ -294,10 +303,10 @@ UserVm createBasicSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering s * available. */ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, - List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, + List securityGroupIdList, Account owner, String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskInfoList, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, - Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, String vmType) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; + Map dataDiskTemplateToDiskOfferingMap, Map userVmOVFProperties, boolean dynamicScalingEnabled, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, String vmType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; /** * Creates a User VM in Advanced Zone (Security Group feature is disabled) @@ -366,10 +375,10 @@ UserVm createAdvancedSecurityGroupVirtualMachine(DataCenter zone, ServiceOfferin * available. */ UserVm createAdvancedVirtualMachine(DataCenter zone, ServiceOffering serviceOffering, VirtualMachineTemplate template, List networkIdList, Account owner, - String hostName, String displayName, Long diskOfferingId, Long diskSize, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, + String hostName, String displayName, Long diskOfferingId, Long diskSize, List dataDiskInfoList, String group, HypervisorType hypervisor, HTTPMethod httpmethod, String userData, Long userDataId, String userDataDetails, List sshKeyPairs, Map requestedIps, IpAddresses defaultIps, Boolean displayVm, String keyboard, List affinityGroupIdList, Map customParameters, String customId, Map> dhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, - Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId) + Map templateOvfPropertiesMap, boolean dynamicScalingEnabled, String vmType, Long overrideDiskOfferingId, Long rootDiskKmsKeyId, Volume volume, Snapshot snapshot) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException, StorageUnavailableException, ResourceAllocationException; @@ -519,6 +528,7 @@ UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException * @param userId user ID * @param serviceOffering service offering for the imported VM * @param sshPublicKey ssh key for the imported VM + * @param guestOsId guest OS ID for the imported VM (if not passed, then the guest OS of the template will be used) * @param hostName the name for the imported VM * @param hypervisorType hypervisor type for the imported VM * @param customParameters details for the imported VM @@ -528,13 +538,18 @@ UserVm upgradeVirtualMachine(ScaleVMCmd cmd) throws ResourceUnavailableException * @throws InsufficientCapacityException in case of errors */ UserVm importVM(final DataCenter zone, final Host host, final VirtualMachineTemplate template, final String instanceNameInternal, final String displayName, final Account owner, final String userData, final Account caller, final Boolean isDisplayVm, final String keyboard, - final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKey, + final long accountId, final long userId, final ServiceOffering serviceOffering, final String sshPublicKey, final Long guestOsId, final String hostName, final HypervisorType hypervisorType, final Map customParameters, final VirtualMachine.PowerState powerState, final LinkedHashMap> networkNicMap) throws InsufficientCapacityException; /** * Unmanage a guest VM from CloudStack - * @return true if the VM is successfully unmanaged, false if not. + * + * @return (true if successful, false if not, hostUuid) if the VM is successfully unmanaged. */ - boolean unmanageUserVM(Long vmId); + Pair unmanageUserVM(Long vmId, Long targetHostId); + + UserVm allocateVMFromBackup(CreateVMFromBackupCmd cmd) throws InsufficientCapacityException, ResourceAllocationException, ResourceUnavailableException; + + UserVm restoreVMFromBackup(CreateVMFromBackupCmd cmd) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; } diff --git a/api/src/main/java/com/cloud/vm/VirtualMachine.java b/api/src/main/java/com/cloud/vm/VirtualMachine.java index e2ea408e7b8c..3adcc85d28a1 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachine.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachine.java @@ -58,7 +58,10 @@ public enum State { Error(false, "VM is in error"), Unknown(false, "VM state is unknown."), Shutdown(false, "VM state is shutdown from inside"), - Restoring(true, "VM is being restored from backup"); + Restoring(true, "VM is being restored from backup"), + BackingUp(true, "VM is being backed up"), + BackupError(false, "VM backup is in an inconsistent state. Operator should analyse the logs and restore the VM"), + RestoreError(false, "VM restore left the VM in an inconsistent state. Operator should analyse the logs and restore the VM"); private final boolean _transitional; String _description; @@ -124,14 +127,24 @@ public static StateMachine2 getStat s_fsm.addTransition(new Transition(State.Stopping, VirtualMachine.Event.StopRequested, State.Stopping, null)); s_fsm.addTransition(new Transition(State.Stopping, VirtualMachine.Event.AgentReportShutdowned, State.Stopped, Arrays.asList(new Impact[]{Impact.USAGE}))); s_fsm.addTransition(new Transition(State.Expunging, VirtualMachine.Event.OperationFailed, State.Expunging,null)); + // Note: In addition to the Stopped -> Error transition for failed VM creation, + // a VM can also transition from Expunging to Error on OperationFailedToError. + s_fsm.addTransition(new Transition(State.Expunging, VirtualMachine.Event.OperationFailedToError, State.Error, null)); s_fsm.addTransition(new Transition(State.Expunging, VirtualMachine.Event.ExpungeOperation, State.Expunging,null)); s_fsm.addTransition(new Transition(State.Error, VirtualMachine.Event.DestroyRequested, State.Expunging, null)); s_fsm.addTransition(new Transition(State.Error, VirtualMachine.Event.ExpungeOperation, State.Expunging, null)); s_fsm.addTransition(new Transition(State.Stopped, Event.RestoringRequested, State.Restoring, null)); - s_fsm.addTransition(new Transition(State.Expunging, Event.RestoringRequested, State.Restoring, null)); s_fsm.addTransition(new Transition(State.Destroyed, Event.RestoringRequested, State.Restoring, null)); s_fsm.addTransition(new Transition(State.Restoring, Event.RestoringSuccess, State.Stopped, null)); s_fsm.addTransition(new Transition(State.Restoring, Event.RestoringFailed, State.Stopped, null)); + s_fsm.addTransition(new Transition<>(State.Running, Event.BackupRequested, State.BackingUp, null)); + s_fsm.addTransition(new Transition<>(State.Stopped, Event.BackupRequested, State.BackingUp, null)); + s_fsm.addTransition(new Transition<>(State.BackingUp, Event.BackupSucceededRunning, State.Running, null)); + s_fsm.addTransition(new Transition<>(State.BackingUp, Event.BackupSucceededStopped, State.Stopped, null)); + s_fsm.addTransition(new Transition<>(State.BackingUp, Event.OperationFailedToError, State.BackupError, null)); + s_fsm.addTransition(new Transition<>(State.BackingUp, Event.OperationFailedToRunning, State.Running, null)); + s_fsm.addTransition(new Transition<>(State.BackingUp, Event.OperationFailedToStopped, State.Stopped, null)); + s_fsm.addTransition(new Transition(State.RestoreError, Event.RestoringFailed, State.RestoreError, null)); s_fsm.addTransition(new Transition(State.Starting, VirtualMachine.Event.FollowAgentPowerOnReport, State.Running, Arrays.asList(new Impact[]{Impact.USAGE}))); s_fsm.addTransition(new Transition(State.Stopping, VirtualMachine.Event.FollowAgentPowerOnReport, State.Running, null)); @@ -210,6 +223,8 @@ public enum Event { ExpungeOperation, OperationSucceeded, OperationFailed, + OperationFailedToRunning, + OperationFailedToStopped, OperationFailedToError, OperationRetry, AgentReportShutdowned, @@ -219,6 +234,10 @@ public enum Event { RestoringRequested, RestoringFailed, RestoringSuccess, + BackupRequested, + BackupSucceededStopped, + BackupSucceededRunning, + FinalizedBackupChain, // added for new VMSync logic FollowAgentPowerOnReport, diff --git a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java index c67ee4eabc28..5c78d6bedd64 100644 --- a/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java +++ b/api/src/main/java/com/cloud/vm/VirtualMachineProfile.java @@ -78,6 +78,7 @@ public static class Param { public static final Param BootIntoSetup = new Param("enterHardwareSetup"); public static final Param PreserveNics = new Param("PreserveNics"); public static final Param ConsiderLastHost = new Param("ConsiderLastHost"); + public static final Param ReturnAfterVolumePrepare = new Param("ReturnAfterVolumePrepare"); private String name; diff --git a/api/src/main/java/com/cloud/vm/VmDetailConstants.java b/api/src/main/java/com/cloud/vm/VmDetailConstants.java index 3d0152b0e438..877df55c6d67 100644 --- a/api/src/main/java/com/cloud/vm/VmDetailConstants.java +++ b/api/src/main/java/com/cloud/vm/VmDetailConstants.java @@ -41,6 +41,7 @@ public interface VmDetailConstants { String KVM_VNC_PORT = "kvm.vnc.port"; String KVM_VNC_ADDRESS = "kvm.vnc.address"; String KVM_VNC_PASSWORD = "kvm.vnc.password"; + String KVM_GUEST_OS_MACHINE_TYPE = "kvm.guest.os.machine.type"; // KVM specific, custom virtual GPU hardware String VIDEO_HARDWARE = "video.hardware"; @@ -92,6 +93,10 @@ public interface VmDetailConstants { String DEPLOY_AS_IS_CONFIGURATION = "configurationId"; String KEY_PAIR_NAMES = "keypairnames"; String CKS_CONTROL_NODE_LOGIN_USER = "controlNodeLoginUser"; + String CKS_NODE_TYPE = "node"; + String OFFERING = "offering"; + String TEMPLATE = "template"; + String AFFINITY_GROUP = "affinitygroup"; // VMware to KVM VM migrations specific String VMWARE_TO_KVM_PREFIX = "vmware-to-kvm"; @@ -113,4 +118,32 @@ public interface VmDetailConstants { // CPU mode and model, ADMIN only String GUEST_CPU_MODE = "guest.cpu.mode"; String GUEST_CPU_MODEL = "guest.cpu.model"; + + // Lease related + String INSTANCE_LEASE_EXPIRY_DATE = "leaseexpirydate"; + String INSTANCE_LEASE_EXPIRY_ACTION = "leaseexpiryaction"; + String INSTANCE_LEASE_EXECUTION = "leaseactionexecution"; + + // External orchestrator related + String MAC_ADDRESS = "mac_address"; + String EXPUNGE_EXTERNAL_VM = "expunge.external.vm"; + String EXTERNAL_DETAIL_PREFIX = "External:"; + String CLOUDSTACK_VM_DETAILS = "cloudstack.vm.details"; + String CLOUDSTACK_VLAN = "cloudstack.vlan"; + + // KVM Checkpoints related + String ACTIVE_CHECKPOINT_ID = "active.checkpoint.id"; + String ACTIVE_CHECKPOINT_CREATE_TIME = "active.checkpoint.create.time"; + String LAST_CHECKPOINT_ID = "last.checkpoint.id"; + String LAST_CHECKPOINT_CREATE_TIME = "last.checkpoint.create.time"; + + // KBOSS specific + String LINKED_VOLUMES_SECONDARY_STORAGE_UUIDS = "linkedVolumesSecondaryStorageUuids"; + String VALIDATION_COMMAND = "backupValidationCommand"; + String VALIDATION_COMMAND_ARGUMENTS = "backupValidationCommandArguments"; + String VALIDATION_COMMAND_EXPECTED_RESULT = "backupValidationCommandExpectedResult"; + String VALIDATION_COMMAND_TIMEOUT = "backupValidationCommandTimeout"; + String VALIDATION_SCREENSHOT_WAIT = "backupValidationScreenshotWait"; + String VALIDATION_BOOT_TIMEOUT = "backupValidationBootTimeout"; + String LAST_KNOWN_STATE = "last_known_state"; } diff --git a/api/src/main/java/com/cloud/vm/VmDiskInfo.java b/api/src/main/java/com/cloud/vm/VmDiskInfo.java new file mode 100644 index 000000000000..97683e8397fa --- /dev/null +++ b/api/src/main/java/com/cloud/vm/VmDiskInfo.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.cloud.vm; + +import com.cloud.offering.DiskOffering; +import com.cloud.offering.DiskOfferingInfo; + +public class VmDiskInfo extends DiskOfferingInfo { + private Long _deviceId; + + public VmDiskInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops) { + super(diskOffering, size, minIops, maxIops); + } + + public VmDiskInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long deviceId) { + super(diskOffering, size, minIops, maxIops); + _deviceId = deviceId; + } + + public VmDiskInfo(DiskOffering diskOffering, Long size, Long minIops, Long maxIops, Long deviceId, Long kmsKeyId) { + super(diskOffering, size, minIops, maxIops, kmsKeyId); + _deviceId = deviceId; + } + + public Long getDeviceId() { + return _deviceId; + } +} diff --git a/api/src/main/java/com/cloud/vm/snapshot/VMSnapshot.java b/api/src/main/java/com/cloud/vm/snapshot/VMSnapshot.java index 4e1579e05658..b4b144e2f8af 100644 --- a/api/src/main/java/com/cloud/vm/snapshot/VMSnapshot.java +++ b/api/src/main/java/com/cloud/vm/snapshot/VMSnapshot.java @@ -31,7 +31,8 @@ public interface VMSnapshot extends ControlledEntity, Identity, InternalIdentity enum State { Allocated("The Instance Snapshot is allocated but has not been created yet."), Creating("The Instance Snapshot is being created."), Ready( "The Instance Snapshot is ready to be used."), Reverting("The Instance Snapshot is being used to revert"), Expunging("The volume is being expunging"), Removed( - "The volume is destroyed, and can't be recovered."), Error("The volume is in error state, and can't be recovered"); + "The volume is destroyed, and can't be recovered."), Error("The volume is in error state, and can't be recovered"), + Hidden("The Instance snapshot is hidden from the user and cannot be recovered."); String _description; @@ -60,6 +61,8 @@ public String getDescription() { s_fsm.addTransition(Expunging, Event.ExpungeRequested, Expunging); s_fsm.addTransition(Expunging, Event.OperationSucceeded, Removed); s_fsm.addTransition(Expunging, Event.OperationFailed, Error); + s_fsm.addTransition(Expunging, Event.Hide, Hidden); + s_fsm.addTransition(Hidden, Event.ExpungeRequested, Expunging); } } @@ -68,7 +71,7 @@ enum Type { } enum Event { - CreateRequested, OperationFailed, OperationSucceeded, RevertRequested, ExpungeRequested, + CreateRequested, OperationFailed, OperationSucceeded, RevertRequested, ExpungeRequested, Hide, } @Override diff --git a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java index 660f64f43ef2..286a3598e4fb 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/APIChecker.java @@ -20,6 +20,7 @@ import com.cloud.user.Account; import com.cloud.user.User; import com.cloud.utils.component.Adapter; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; import java.util.List; @@ -31,8 +32,8 @@ public interface APIChecker extends Adapter { // If true, apiChecker has checked the operation // If false, apiChecker is unable to handle the operation or not implemented // On exception, checkAccess failed don't allow - boolean checkAccess(User user, String apiCommandName) throws PermissionDeniedException; - boolean checkAccess(Account account, String apiCommandName) throws PermissionDeniedException; + boolean checkAccess(User user, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException; + boolean checkAccess(Account account, String apiCommandName, ApiKeyPairPermission... apiKeyPairPermissions) throws PermissionDeniedException; /** * Verifies if the account has permission for the given list of APIs and returns only the allowed ones. * @@ -43,4 +44,5 @@ public interface APIChecker extends Adapter { */ List getApisAllowedToUser(Role role, User user, List apiNames) throws PermissionDeniedException; boolean isEnabled(); + List getImplicitRolePermissions(RoleType roleType); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java index 251c6b6d3f9e..f382b1c6964f 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RolePermissionEntity.java @@ -21,7 +21,7 @@ import org.apache.cloudstack.api.InternalIdentity; public interface RolePermissionEntity extends InternalIdentity, Identity { - public enum Permission { + enum Permission { ALLOW, DENY } Rule getRule(); diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java index f041c8342aec..14e0a608a925 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleService.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleService.java @@ -104,5 +104,26 @@ public interface RoleService { List findAllPermissionsBy(Long roleId); + List findAllRolePermissionsEntityBy(Long roleId, boolean considerImplicitRules); + Permission getRolePermission(String permission); + + int removeRolesIfNeeded(List roles); + + /** + * Checks if the role of the caller account has compatible permissions of the specified role permissions. + * For each permission of the {@param rolePermissionsToAccess}, the role of the caller needs to contain the same permission. + * + * @param rolePermissions the permissions of the caller role. + * @param rolePermissionsToAccess the permissions for the role that the caller role wants to access. + * @return True if the role can be accessed with the given permissions; false otherwise. + */ + boolean roleHasPermission(Map rolePermissions, List rolePermissionsToAccess); + + /** + * Given a list of role permissions, returns a {@link Map} containing the API name as the key and the {@link RolePermissionEntity} for the API as the value. + * + * @param rolePermissions Permissions for the role from role. + */ + Map getRoleRulesAndPermissions(List rolePermissions); } diff --git a/api/src/main/java/org/apache/cloudstack/acl/RoleType.java b/api/src/main/java/org/apache/cloudstack/acl/RoleType.java index 6d351649c0b1..c33488cd9239 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/RoleType.java +++ b/api/src/main/java/org/apache/cloudstack/acl/RoleType.java @@ -23,8 +23,11 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import java.util.Collection; +import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.Set; // Enum for default roles in CloudStack public enum RoleType { @@ -100,15 +103,39 @@ public static Long getRoleByAccountType(final Long roleId, final Account.Type ac return roleId; } + public static int toCombinedMask(Collection roles) { + int combinedMask = 0; + if (roles != null) { + for (RoleType role : roles) { + combinedMask |= role.getMask(); + } + } + return combinedMask; + } + + public static Set fromCombinedMask(int combinedMask) { + Set roles = EnumSet.noneOf(RoleType.class); + for (RoleType roleType : RoleType.values()) { + if ((combinedMask & roleType.getMask()) != 0) { + roles.add(roleType); + } + } + if (roles.isEmpty()) { + roles.add(Unknown); + } + return roles; + } + + /** * This method returns the role account type if the role isn't null, else it returns the default account type. * */ public static Account.Type getAccountTypeByRole(final Role role, final Account.Type defautAccountType) { if (role != null) { - LOGGER.debug(String.format("Role [%s] is not null; therefore, we use its Account type [%s].", role, defautAccountType)); + LOGGER.debug("Role [{}] is not null; therefore, we use its Account type [{}].", role, defautAccountType); return role.getRoleType().getAccountType(); } - LOGGER.debug(String.format("Role is null; therefore, we use the default Account type [%s] value.", defautAccountType)); + LOGGER.debug("Role is null; therefore, we use the default Account type [{}] value.", defautAccountType); return defautAccountType; } } diff --git a/api/src/main/java/org/apache/cloudstack/acl/Rule.java b/api/src/main/java/org/apache/cloudstack/acl/Rule.java index a4ef7773f67b..ad01825a95f1 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/Rule.java +++ b/api/src/main/java/org/apache/cloudstack/acl/Rule.java @@ -25,16 +25,18 @@ public final class Rule { private final String rule; + private final Pattern matchingPattern; private final static Pattern ALLOWED_PATTERN = Pattern.compile("^[a-zA-Z0-9*]+$"); public Rule(final String rule) { validate(rule); this.rule = rule; + matchingPattern = Pattern.compile(rule.toLowerCase().replace("*", "(\\w*\\*?)+")); } public boolean matches(final String commandName) { - return StringUtils.isNotEmpty(commandName) - && commandName.toLowerCase().matches(rule.toLowerCase().replace("*", "\\w*")); + return StringUtils.isNotEmpty(commandName) && + matchingPattern.matcher(commandName.toLowerCase()).matches(); } public String getRuleString() { diff --git a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java index 82a8ec5fe932..fa17df7c6ed4 100644 --- a/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java +++ b/api/src/main/java/org/apache/cloudstack/acl/SecurityChecker.java @@ -27,6 +27,8 @@ import com.cloud.user.User; import com.cloud.utils.component.Adapter; +import org.apache.cloudstack.backup.BackupOffering; + /** * SecurityChecker checks the ownership and access control to objects within */ @@ -145,4 +147,6 @@ boolean checkAccess(Account caller, AccessType accessType, String action, Contro boolean checkAccess(Account account, NetworkOffering nof, DataCenter zone) throws PermissionDeniedException; boolean checkAccess(Account account, VpcOffering vof, DataCenter zone) throws PermissionDeniedException; + + boolean checkAccess(Account account, BackupOffering bof) throws PermissionDeniedException; } diff --git a/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPair.java b/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPair.java new file mode 100644 index 000000000000..ecce0ae50824 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPair.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.acl.apikeypair; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface ApiKeyPair extends ControlledEntity, InternalIdentity, Identity { + Long getUserId(); + Date getStartDate(); + Date getEndDate(); + Date getCreated(); + String getDescription(); + String getApiKey(); + String getSecretKey(); + String getName(); + Date getRemoved(); + void setRemoved(Date date); + void validateDate(); + boolean hasEndDatePassed(); +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPairPermission.java b/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPairPermission.java new file mode 100644 index 000000000000..60b3834cc073 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPairPermission.java @@ -0,0 +1,23 @@ +// 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.acl.apikeypair; + +import org.apache.cloudstack.acl.RolePermissionEntity; + +public interface ApiKeyPairPermission extends RolePermissionEntity { + long getApiKeyPairId(); +} diff --git a/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPairService.java b/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPairService.java new file mode 100644 index 000000000000..de9c829b17dc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/acl/apikeypair/ApiKeyPairService.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.acl.apikeypair; + +import java.util.List; + +public interface ApiKeyPairService { + List findAllPermissionsByKeyPairId(Long apiKeyPairId, Long roleId); + + ApiKeyPair findByApiKey(String apiKey); + + ApiKeyPair findById(Long id); +} diff --git a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupService.java b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupService.java index 018e5f5bab5a..03992c0c1c7c 100644 --- a/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupService.java +++ b/api/src/main/java/org/apache/cloudstack/affinity/AffinityGroupService.java @@ -66,5 +66,4 @@ public interface AffinityGroupService { boolean isAffinityGroupAvailableInDomain(long affinityGroupId, long domainId); - } diff --git a/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java b/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java index 9995d8039e1f..96ca35f264ca 100644 --- a/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java +++ b/api/src/main/java/org/apache/cloudstack/affinity/AffinityProcessorBase.java @@ -29,6 +29,9 @@ public class AffinityProcessorBase extends AdapterBase implements AffinityGroupProcessor { + public static final String AFFINITY_TYPE_HOST = "host affinity"; + public static final String AFFINITY_TYPE_HOST_ANTI = "host anti-affinity"; + protected String _type; @Override diff --git a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java index 4ae6288efce4..a9c2abc11ce7 100644 --- a/api/src/main/java/org/apache/cloudstack/alert/AlertService.java +++ b/api/src/main/java/org/apache/cloudstack/alert/AlertService.java @@ -24,18 +24,24 @@ public interface AlertService { public static class AlertType { - private static Set defaultAlertTypes = new HashSet(); + private static final Set defaultAlertTypes = new HashSet<>(); private final String name; private final short type; + private final boolean repetitionAllowed; - private AlertType(short type, String name, boolean isDefault) { + private AlertType(short type, String name, boolean isDefault, boolean repetitionAllowed) { this.name = name; this.type = type; + this.repetitionAllowed = repetitionAllowed; if (isDefault) { defaultAlertTypes.add(this); } } + private AlertType(short type, String name, boolean isDefault) { + this(type, name, isDefault, false); + } + public static final AlertType ALERT_TYPE_MEMORY = new AlertType(Capacity.CAPACITY_TYPE_MEMORY, "ALERT.MEMORY", true); public static final AlertType ALERT_TYPE_CPU = new AlertType(Capacity.CAPACITY_TYPE_CPU, "ALERT.CPU", true); public static final AlertType ALERT_TYPE_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_STORAGE, "ALERT.STORAGE", true); @@ -45,34 +51,41 @@ private AlertType(short type, String name, boolean isDefault) { public static final AlertType ALERT_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET = new AlertType(Capacity.CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET, "ALERT.NETWORK.IPV6SUBNET", true); public static final AlertType ALERT_TYPE_PRIVATE_IP = new AlertType(Capacity.CAPACITY_TYPE_PRIVATE_IP, "ALERT.NETWORK.PRIVATEIP", true); public static final AlertType ALERT_TYPE_SECONDARY_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_SECONDARY_STORAGE, "ALERT.STORAGE.SECONDARY", true); - public static final AlertType ALERT_TYPE_HOST = new AlertType((short)7, "ALERT.COMPUTE.HOST", true); - public static final AlertType ALERT_TYPE_USERVM = new AlertType((short)8, "ALERT.USERVM", true); - public static final AlertType ALERT_TYPE_DOMAIN_ROUTER = new AlertType((short)9, "ALERT.SERVICE.DOMAINROUTER", true); - public static final AlertType ALERT_TYPE_CONSOLE_PROXY = new AlertType((short)10, "ALERT.SERVICE.CONSOLEPROXY", true); + public static final AlertType ALERT_TYPE_HOST = new AlertType((short)7, "ALERT.COMPUTE.HOST", true, true); + public static final AlertType ALERT_TYPE_USERVM = new AlertType((short)8, "ALERT.USERVM", true, true); + public static final AlertType ALERT_TYPE_DOMAIN_ROUTER = new AlertType((short)9, "ALERT.SERVICE.DOMAINROUTER", true, true); + public static final AlertType ALERT_TYPE_CONSOLE_PROXY = new AlertType((short)10, "ALERT.SERVICE.CONSOLEPROXY", true, true); public static final AlertType ALERT_TYPE_ROUTING = new AlertType((short)11, "ALERT.NETWORK.ROUTING", true); - public static final AlertType ALERT_TYPE_STORAGE_MISC = new AlertType((short)12, "ALERT.STORAGE.MISC", true); + public static final AlertType ALERT_TYPE_STORAGE_MISC = new AlertType((short)12, "ALERT.STORAGE.MISC", true, true); public static final AlertType ALERT_TYPE_USAGE_SERVER = new AlertType((short)13, "ALERT.USAGE", true); - public static final AlertType ALERT_TYPE_MANAGEMENT_NODE = new AlertType((short)14, "ALERT.MANAGEMENT", true); + public static final AlertType ALERT_TYPE_MANAGEMENT_NODE = new AlertType((short)14, "ALERT.MANAGEMENT", true, true); public static final AlertType ALERT_TYPE_DOMAIN_ROUTER_MIGRATE = new AlertType((short)15, "ALERT.NETWORK.DOMAINROUTERMIGRATE", true); public static final AlertType ALERT_TYPE_CONSOLE_PROXY_MIGRATE = new AlertType((short)16, "ALERT.SERVICE.CONSOLEPROXYMIGRATE", true); public static final AlertType ALERT_TYPE_USERVM_MIGRATE = new AlertType((short)17, "ALERT.USERVM.MIGRATE", true); public static final AlertType ALERT_TYPE_VLAN = new AlertType((short)18, "ALERT.NETWORK.VLAN", true); - public static final AlertType ALERT_TYPE_SSVM = new AlertType((short)19, "ALERT.SERVICE.SSVM", true); + public static final AlertType ALERT_TYPE_SSVM = new AlertType((short)19, "ALERT.SERVICE.SSVM", true, true); public static final AlertType ALERT_TYPE_USAGE_SERVER_RESULT = new AlertType((short)20, "ALERT.USAGE.RESULT", true); public static final AlertType ALERT_TYPE_STORAGE_DELETE = new AlertType((short)21, "ALERT.STORAGE.DELETE", true); public static final AlertType ALERT_TYPE_UPDATE_RESOURCE_COUNT = new AlertType((short)22, "ALERT.RESOURCE.COUNT", true); public static final AlertType ALERT_TYPE_USAGE_SANITY_RESULT = new AlertType((short)23, "ALERT.USAGE.SANITY", true); public static final AlertType ALERT_TYPE_DIRECT_ATTACHED_PUBLIC_IP = new AlertType((short)24, "ALERT.NETWORK.DIRECTPUBLICIP", true); public static final AlertType ALERT_TYPE_LOCAL_STORAGE = new AlertType((short)25, "ALERT.STORAGE.LOCAL", true); - public static final AlertType ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED = new AlertType((short)26, "ALERT.RESOURCE.EXCEED", true); + public static final AlertType ALERT_TYPE_RESOURCE_LIMIT_EXCEEDED = new AlertType((short)26, "ALERT.RESOURCE.EXCEED", true, true); public static final AlertType ALERT_TYPE_SYNC = new AlertType((short)27, "ALERT.TYPE.SYNC", true); - public static final AlertType ALERT_TYPE_UPLOAD_FAILED = new AlertType((short)28, "ALERT.UPLOAD.FAILED", true); - public static final AlertType ALERT_TYPE_OOBM_AUTH_ERROR = new AlertType((short)29, "ALERT.OOBM.AUTHERROR", true); - public static final AlertType ALERT_TYPE_HA_ACTION = new AlertType((short)30, "ALERT.HA.ACTION", true); - public static final AlertType ALERT_TYPE_CA_CERT = new AlertType((short)31, "ALERT.CA.CERT", true); + public static final AlertType ALERT_TYPE_UPLOAD_FAILED = new AlertType((short)28, "ALERT.UPLOAD.FAILED", true, true); + public static final AlertType ALERT_TYPE_OOBM_AUTH_ERROR = new AlertType((short)29, "ALERT.OOBM.AUTHERROR", true, true); + public static final AlertType ALERT_TYPE_HA_ACTION = new AlertType((short)30, "ALERT.HA.ACTION", true, true); + public static final AlertType ALERT_TYPE_CA_CERT = new AlertType((short)31, "ALERT.CA.CERT", true, true); public static final AlertType ALERT_TYPE_VM_SNAPSHOT = new AlertType((short)32, "ALERT.VM.SNAPSHOT", true); public static final AlertType ALERT_TYPE_VR_PUBLIC_IFACE_MTU = new AlertType((short)33, "ALERT.VR.PUBLIC.IFACE.MTU", true); public static final AlertType ALERT_TYPE_VR_PRIVATE_IFACE_MTU = new AlertType((short)34, "ALERT.VR.PRIVATE.IFACE.MTU", true); + public static final AlertType ALERT_TYPE_EXTENSION_PATH_NOT_READY = new AlertType((short)33, "ALERT.TYPE.EXTENSION.PATH.NOT.READY", true, true); + public static final AlertType ALERT_TYPE_VPN_GATEWAY_OBSOLETE_PARAMETERS = new AlertType((short)34, "ALERT.S2S.VPN.GATEWAY.OBSOLETE.PARAMETERS", true, true); + public static final AlertType ALERT_TYPE_BACKUP_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_BACKUP_STORAGE, "ALERT.STORAGE.BACKUP", true); + public static final AlertType ALERT_TYPE_OBJECT_STORAGE = new AlertType(Capacity.CAPACITY_TYPE_OBJECT_STORAGE, "ALERT.STORAGE.OBJECT", true); + public static final AlertType ALERT_TYPE_BACKUP_VALIDATION_FAILED = new AlertType((short)35, "ALERT.BACKUP.VALIDATION.FAILED", true, true); + public static final AlertType ALERT_TYPE_BACKUP_VALIDATION_UNABLE_TO_VALIDATE = new AlertType((short)36, "ALERT.BACKUP.VALIDATION.UNABLE.TO.VALIDATE", true, true); + public static final AlertType ALERT_TYPE_BACKUP_VALIDATION_CLEANUP_FAILED = new AlertType((short)37, "ALERT.BACKUP.VALIDATION.CLEANUP_FAILED", true, true); public short getType() { return type; @@ -82,6 +95,10 @@ public String getName() { return name; } + public boolean isRepetitionAllowed() { + return repetitionAllowed; + } + private static AlertType getAlertType(short type) { for (AlertType alertType : defaultAlertTypes) { if (alertType.getType() == type) { @@ -105,7 +122,7 @@ public static AlertType generateAlert(short type, String name) { if (defaultAlert != null && !defaultAlert.getName().equalsIgnoreCase(name)) { throw new InvalidParameterValueException("There is a default alert having type " + type + " and name " + defaultAlert.getName()); } else { - return new AlertType(type, name, false); + return new AlertType(type, name, false, false); } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/APICommand.java b/api/src/main/java/org/apache/cloudstack/api/APICommand.java index c559be081165..b77649046ca9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/APICommand.java +++ b/api/src/main/java/org/apache/cloudstack/api/APICommand.java @@ -50,4 +50,6 @@ RoleType[] authorized() default {}; Class[] entityType() default {}; + + String httpMethod() default ""; } diff --git a/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java b/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java index 1abdc0bbcc4a..13f351f3a27f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/AbstractGetUploadParamsCmd.java @@ -81,6 +81,34 @@ public Long getProjectId() { return projectId; } + public void setName(String name) { + this.name = name; + } + + public void setFormat(String format) { + this.format = format; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public void setChecksum(String checksum) { + this.checksum = checksum; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + public GetUploadParamsResponse createGetUploadParamsResponse(UUID id, URL postURL, String metadata, String timeout, String signature) { return new GetUploadParamsResponse(id, postURL, metadata, timeout, signature); } diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java index f2f52cec9697..2aa97b65a3d5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiCommandResourceType.java @@ -61,6 +61,7 @@ public enum ApiCommandResourceType { AffinityGroup(org.apache.cloudstack.affinity.AffinityGroup.class), InternalLbVm(com.cloud.network.router.VirtualRouter.class), DedicatedGuestVlanRange(com.cloud.network.GuestVlan.class), + GuestOsCategory(com.cloud.storage.GuestOsCategory.class), GuestOs(com.cloud.storage.GuestOS.class), GuestOsMapping(com.cloud.storage.GuestOSHypervisor.class), Network(com.cloud.network.Network.class), @@ -84,9 +85,13 @@ public enum ApiCommandResourceType { ObjectStore(org.apache.cloudstack.storage.object.ObjectStore.class), Bucket(org.apache.cloudstack.storage.object.Bucket.class), QuotaTariff(org.apache.cloudstack.quota.QuotaTariff.class), - KubernetesCluster(null), + KubernetesCluster(com.cloud.kubernetes.cluster.KubernetesCluster.class), KubernetesSupportedVersion(null), - SharedFS(org.apache.cloudstack.storage.sharedfs.SharedFS.class); + SharedFS(org.apache.cloudstack.storage.sharedfs.SharedFS.class), + Extension(org.apache.cloudstack.extension.Extension.class), + ExtensionCustomAction(org.apache.cloudstack.extension.ExtensionCustomAction.class), + KmsKey(org.apache.cloudstack.kms.KMSKey.class), + HsmProfile(org.apache.cloudstack.kms.HSMProfile.class); private final Class clazz; @@ -124,8 +129,8 @@ public String toString() { } public static ApiCommandResourceType fromString(String value) { - if (StringUtils.isNotEmpty(value) && EnumUtils.isValidEnum(ApiCommandResourceType.class, value)) { - return valueOf(value); + if (StringUtils.isNotBlank(value) && EnumUtils.isValidEnumIgnoreCase(ApiCommandResourceType.class, value)) { + return EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, value); } return null; } 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 daf1bdc705de..ac6acdf42516 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiConstants.java @@ -19,10 +19,13 @@ public class ApiConstants { public static final String ACCOUNT = "account"; public static final String ACCOUNTS = "accounts"; + public static final String ACCOUNT_NAME = "accountname"; + public static final String ACCOUNT_STATE_TO_SHOW = "accountstatetoshow"; public static final String ACCOUNT_TYPE = "accounttype"; public static final String ACCOUNT_ID = "accountid"; public static final String ACCOUNT_IDS = "accountids"; public static final String ACCUMULATE = "accumulate"; + public static final String ACQUIRED = "acquired"; public static final String ACTIVATION_RULE = "activationrule"; public static final String ACTIVITY = "activity"; public static final String ADAPTER_TYPE = "adaptertype"; @@ -30,9 +33,11 @@ public class ApiConstants { public static final String ADDRESS = "address"; public static final String ALGORITHM = "algorithm"; public static final String ALIAS = "alias"; + public static final String ALLOCATED = "allocated"; public static final String ALLOCATED_DATE = "allocateddate"; public static final String ALLOCATED_ONLY = "allocatedonly"; public static final String ALLOCATED_TIME = "allocated"; + public static final String ALLOWED_ROLE_TYPES = "allowedroletypes"; public static final String ALLOW_USER_FORCE_STOP_VM = "allowuserforcestopvm"; public static final String ANNOTATION = "annotation"; public static final String API_KEY = "apikey"; @@ -43,6 +48,7 @@ public class ApiConstants { public static final String AS_NUMBER_ID = "asnumberid"; public static final String ASN_RANGE = "asnrange"; public static final String ASN_RANGE_ID = "asnrangeid"; + public static final String API_KEY_FILTER = "apikeyfilter"; public static final String ASYNC_BACKUP = "asyncbackup"; public static final String AUTO_SELECT = "autoselect"; public static final String USER_API_KEY = "userapikey"; @@ -52,9 +58,20 @@ public class ApiConstants { public static final String AVAILABLE = "available"; public static final String AVAILABLE_SUBNETS = "availablesubnets"; public static final String AVAILABLE_VIRTUAL_MACHINE_COUNT = "availablevirtualmachinecount"; + public static final String BACKUP_AVAILABLE = "backupavailable"; public static final String BACKUP_ID = "backupid"; + public static final String BACKUP_LIMIT = "backuplimit"; public static final String BACKUP_OFFERING_NAME = "backupofferingname"; public static final String BACKUP_OFFERING_ID = "backupofferingid"; + public static final String BACKUP_OFFERING_DETAILS = "backupofferingdetails"; + public static final String BACKUP_STORAGE_AVAILABLE = "backupstorageavailable"; + public static final String BACKUP_STORAGE_LIMIT = "backupstoragelimit"; + public static final String BACKUP_STORAGE_TOTAL = "backupstoragetotal"; + 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"; @@ -63,6 +80,8 @@ public class ApiConstants { public static final String BOOTABLE = "bootable"; public static final String BIND_DN = "binddn"; public static final String BIND_PASSWORD = "bindpass"; + public static final String BLANK_INSTANCE = "blankinstance"; + public static final String BUS_ADDRESS = "busaddress"; public static final String BYTES_READ_RATE = "bytesreadrate"; public static final String BYTES_READ_RATE_MAX = "bytesreadratemax"; public static final String BYTES_READ_RATE_MAX_LENGTH = "bytesreadratemaxlength"; @@ -70,6 +89,7 @@ public class ApiConstants { public static final String BYTES_WRITE_RATE_MAX = "byteswriteratemax"; public static final String BYTES_WRITE_RATE_MAX_LENGTH = "byteswriteratemaxlength"; public static final String BYPASS_VLAN_OVERLAP_CHECK = "bypassvlanoverlapcheck"; + public static final String CALLER = "caller"; public static final String CAPACITY = "capacity"; public static final String CATEGORY = "category"; public static final String CAN_REVERT = "canrevert"; @@ -85,9 +105,13 @@ public class ApiConstants { public static final String CONVERT_INSTANCE_HOST_ID = "convertinstancehostid"; public static final String CONVERT_INSTANCE_STORAGE_POOL_ID = "convertinstancepoolid"; public static final String ENABLED_REVOCATION_CHECK = "enabledrevocationcheck"; + public static final String CLIENT_ADDRESS = "clientaddress"; + public static final String COMBINED_CAPACITY_ORDERING = "COMBINED"; public static final String CONTROLLER = "controller"; public static final String CONTROLLER_UNIT = "controllerunit"; + public static final String CONSOLE_ENDPOINT_CREATOR_ADDRESS = "consoleendpointcreatoraddress"; public static final String COPY_IMAGE_TAGS = "copyimagetags"; + public static final String CPU_OVERCOMMIT_RATIO = "cpuOvercommitRatio"; public static final String CSR = "csr"; public static final String PRIVATE_KEY = "privatekey"; public static final String DATASTORE_HOST = "datastorehost"; @@ -114,12 +138,19 @@ public class ApiConstants { public static final String CN = "cn"; public static final String COMMAND = "command"; public static final String CMD_EVENT_TYPE = "cmdeventtype"; + public static final String CNI_CONFIG = "cniconfig"; + public static final String CNI_CONFIG_ID = "cniconfigurationid"; + public static final String CNI_CONFIG_DETAILS = "cniconfigdetails"; + public static final String CNI_CONFIG_NAME = "cniconfigname"; + public static final String CSI_ENABLED = "csienabled"; public static final String COMPONENT = "component"; + public static final String CPU = "CPU"; public static final String CPU_CORE_PER_SOCKET = "cpucorepersocket"; public static final String CPU_NUMBER = "cpunumber"; public static final String CPU_SPEED = "cpuspeed"; public static final String CPU_LOAD_AVERAGE = "cpuloadaverage"; public static final String CREATED = "created"; + public static final String CROSS_ZONE_INSTANCE_CREATION = "crosszoneinstancecreation"; public static final String CTX_ACCOUNT_ID = "ctxaccountid"; public static final String CTX_DETAILS = "ctxDetails"; public static final String CTX_USER_ID = "ctxuserid"; @@ -128,18 +159,23 @@ public class ApiConstants { public static final String CUSTOMIZED = "customized"; public static final String CUSTOMIZED_IOPS = "customizediops"; public static final String CUSTOM_ID = "customid"; + public static final String CUSTOM_ACTION_ID = "customactionid"; public static final String CUSTOM_JOB_ID = "customjobid"; + public static final String CURRENCY = "currency"; public static final String CURRENT_START_IP = "currentstartip"; public static final String CURRENT_END_IP = "currentendip"; public static final String ENCRYPT = "encrypt"; public static final String ENCRYPT_FORMAT = "encryptformat"; public static final String ENCRYPT_ROOT = "encryptroot"; public static final String ENCRYPTION_SUPPORTED = "encryptionsupported"; + public static final String ETCD_IPS = "etcdips"; public static final String MIN_IOPS = "miniops"; public static final String MAX_IOPS = "maxiops"; public static final String HYPERVISOR_SNAPSHOT_RESERVE = "hypervisorsnapshotreserve"; 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"; @@ -147,10 +183,13 @@ public class ApiConstants { public static final String DESTINATION_ZONE_ID = "destzoneid"; public static final String DETAILS = "details"; public static final String DEVICE_ID = "deviceid"; + public static final String DEVICE_IDS = "deviceids"; + public static final String DEVICE_NAME = "devicename"; public static final String DIRECT_DOWNLOAD = "directdownload"; public static final String DISK = "disk"; public static final String DISK_OFFERING_ID = "diskofferingid"; public static final String NEW_DISK_OFFERING_ID = "newdiskofferingid"; + public static final String ORCHESTRATOR_REQUIRES_PREPARE_VM = "orchestratorrequirespreparevm"; public static final String OVERRIDE_DISK_OFFERING_ID = "overridediskofferingid"; public static final String DISK_KBS_READ = "diskkbsread"; public static final String DISK_KBS_WRITE = "diskkbswrite"; @@ -164,6 +203,7 @@ public class ApiConstants { public static final String UTILIZATION = "utilization"; public static final String DRIVER = "driver"; public static final String ROOT_DISK_SIZE = "rootdisksize"; + public static final String ROOT_DISK_KMS_KEY_ID = "rootdiskkmskeyid"; public static final String DHCP_OPTIONS_NETWORK_LIST = "dhcpoptionsnetworklist"; public static final String DHCP_OPTIONS = "dhcpoptions"; public static final String DHCP_PREFIX = "dhcp:"; @@ -183,36 +223,51 @@ public class ApiConstants { public static final String DOMAIN_PATH = "domainpath"; public static final String DOMAIN_ID = "domainid"; public static final String DOMAIN__ID = "domainId"; + public static final String DUMMY = "dummy"; public static final String DURATION = "duration"; public static final String ELIGIBLE = "eligible"; public static final String EMAIL = "email"; + public static final String ENABLE_CSI = "enablecsi"; public static final String END_ASN = "endasn"; public static final String END_DATE = "enddate"; public static final String END_IP = "endip"; public static final String END_IPV6 = "endipv6"; public static final String END_PORT = "endport"; public static final String ENTRY_TIME = "entrytime"; + public static final String ERROR_MESSAGE = "errormessage"; public static final String EVENT_ID = "eventid"; public static final String EVENT_TYPE = "eventtype"; public static final String EXPIRES = "expires"; public static final String EXTRA_CONFIG = "extraconfig"; + public static final String EXTRA_PARAMS = "extraparams"; public static final String EXTRA_DHCP_OPTION = "extradhcpoption"; public static final String EXTRA_DHCP_OPTION_NAME = "extradhcpoptionname"; public static final String EXTRA_DHCP_OPTION_CODE = "extradhcpoptioncode"; public static final String EXTRA_DHCP_OPTION_VALUE = "extradhcpvalue"; public static final String EXTERNAL = "external"; public static final String EXTERNAL_UUID = "externaluuid"; + public static final String EXTERNAL_DETAILS = "externaldetails"; + public static final String PARAMETERS = "parameters"; + public static final String EXTENSION = "extension"; + public static final String EXTENSION_ID = "extensionid"; + public static final String EXTENSION_NAME = "extensionname"; + public static final String EXTENSIONS_PATH = "extensionspath"; public static final String FENCE = "fence"; public static final String FETCH_LATEST = "fetchlatest"; public static final String FILESYSTEM = "filesystem"; public static final String FIRSTNAME = "firstname"; public static final String FORCED = "forced"; public static final String FORCED_DESTROY_LOCAL_STORAGE = "forcedestroylocalstorage"; + public static final String FORCE_CONVERT_TO_POOL = "forceconverttopool"; + public static final String FORCE_DELETE_HOST = "forcedeletehost"; public static final String FORCE_MS_TO_IMPORT_VM_FILES = "forcemstoimportvmfiles"; + public static final String FORCE_UPDATE_OS_TYPE = "forceupdateostype"; public static final String FORMAT = "format"; public static final String FOR_VIRTUAL_NETWORK = "forvirtualnetwork"; public static final String FOR_SYSTEM_VMS = "forsystemvms"; + public static final String FOR_PROVIDER = "forprovider"; + public static final String FROM_CHECKPOINT_ID = "fromcheckpointid"; public static final String FULL_PATH = "fullpath"; public static final String GATEWAY = "gateway"; public static final String IP6_GATEWAY = "ip6gateway"; @@ -232,6 +287,7 @@ public class ApiConstants { public static final String HEALTH = "health"; public static final String HEADERS = "headers"; public static final String HIDE_IP_ADDRESS_USAGE = "hideipaddressusage"; + public static final String HISTORY = "history"; public static final String HOST_ID = "hostid"; public static final String HOST_IDS = "hostids"; public static final String HOST_IP = "hostip"; @@ -239,6 +295,7 @@ public class ApiConstants { public static final String HOST = "host"; public static final String HOST_CONTROL_STATE = "hostcontrolstate"; public static final String HOSTS_MAP = "hostsmap"; + public static final String HTTP_REQUEST_TYPE = "httprequesttype"; public static final String HYPERVISOR = "hypervisor"; public static final String INLINE = "inline"; public static final String INSTANCE = "instance"; @@ -257,6 +314,7 @@ public class ApiConstants { public static final String PREVIOUS_OWNER_ID = "previousownerid"; public static final String PREVIOUS_OWNER_NAME = "previousownername"; public static final String NEXT_ACL_RULE_ID = "nextaclruleid"; + public static final String NEXT_HOP = "nexthop"; public static final String MOVE_ACL_CONSISTENCY_HASH = "aclconsistencyhash"; public static final String IMAGE_PATH = "imagepath"; public static final String INSTANCE_CONVERSION_SUPPORTED = "instanceconversionsupported"; @@ -264,7 +322,10 @@ public class ApiConstants { public static final String INTERNAL_DNS2 = "internaldns2"; public static final String INTERNET_PROTOCOL = "internetprotocol"; public static final String INTERVAL_TYPE = "intervaltype"; - public static final String LOCATION_TYPE = "locationtype"; + public static final String INSTANCE_LEASE_DURATION = "leaseduration"; + public static final String INSTANCE_LEASE_ENABLED = "instanceleaseenabled"; + public static final String INSTANCE_LEASE_EXPIRY_ACTION = "leaseexpiryaction"; + public static final String INSTANCE_LEASE_EXPIRY_DATE= "leaseexpirydate"; public static final String IOPS_READ_RATE = "iopsreadrate"; public static final String IOPS_READ_RATE_MAX = "iopsreadratemax"; public static final String IOPS_READ_RATE_MAX_LENGTH = "iopsreadratemaxlength"; @@ -274,11 +335,13 @@ public class ApiConstants { public static final String IP_ADDRESS = "ipaddress"; public static final String IP_ADDRESSES = "ipaddresses"; public static final String IP6_ADDRESS = "ip6address"; + public static final String IP6_ADDRESSES = "ip6addresses"; public static final String IP_ADDRESS_ID = "ipaddressid"; public static final String IS_2FA_ENABLED = "is2faenabled"; public static final String IS_2FA_VERIFIED = "is2faverified"; public static final String IS_2FA_MANDATED = "is2famandated"; + public static final String IS_ACTIVE = "isactive"; public static final String IS_ASYNC = "isasync"; public static final String IP_AVAILABLE = "ipavailable"; public static final String IP_LIMIT = "iplimit"; @@ -291,6 +354,7 @@ public class ApiConstants { public static final String IS_EXTRACTABLE = "isextractable"; public static final String IS_FEATURED = "isfeatured"; public static final String IS_IMPLICIT = "isimplicit"; + public static final String IS_ISO = "isiso"; public static final String IS_PORTABLE = "isportable"; public static final String IS_PUBLIC = "ispublic"; public static final String IS_PERSISTENT = "ispersistent"; @@ -306,32 +370,44 @@ public class ApiConstants { public static final String JOB_STATUS = "jobstatus"; public static final String KEEPALIVE_ENABLED = "keepaliveenabled"; public static final String KERNEL_VERSION = "kernelversion"; + public static final String KEYPAIR_ID = "keypairid"; public static final String KEY = "key"; public static final String LABEL = "label"; public static final String LASTNAME = "lastname"; public static final String LAST_BOOT = "lastboottime"; public static final String LAST_SERVER_START = "lastserverstart"; public static final String LAST_SERVER_STOP = "lastserverstop"; + public static final String LEASED = "leased"; public static final String LEVEL = "level"; public static final String LENGTH = "length"; public static final String LIMIT = "limit"; public static final String LIMIT_CPU_USE = "limitcpuuse"; public static final String LIST_HOSTS = "listhosts"; + public static final String LOCATION_TYPE = "locationtype"; public static final String LOCK = "lock"; public static final String LUN = "lun"; public static final String LBID = "lbruleid"; public static final String LB_PROVIDER = "lbprovider"; public static final String MAC_ADDRESS = "macaddress"; + public static final String MAC_ADDRESSES = "macaddresses"; + public static final String MANUAL_UPGRADE = "manualupgrade"; + public static final String MATCH_TYPE = "matchtype"; public static final String MAX = "max"; public static final String MAX_SNAPS = "maxsnaps"; + public static final String MAX_BACKUPS = "maxbackups"; public static final String MAX_CPU_NUMBER = "maxcpunumber"; public static final String MAX_MEMORY = "maxmemory"; + public static final String MEMORY_OVERCOMMIT_RATIO = "memoryOvercommitRatio"; + public static final String MESSAGE = "message"; public static final String MIN_CPU_NUMBER = "mincpunumber"; public static final String MIN_MEMORY = "minmemory"; public static final String MIGRATION_TYPE = "migrationtype"; + public static final String MIGRATION_JOB_ID = "migrationjobid"; + public static final String MIGRATION_JOB_STATUS = "migrationjobstatus"; public static final String MIGRATIONS = "migrations"; public static final String MEMORY = "memory"; public static final String MODE = "mode"; + public static final String MOUNT_CKS_ISO_ON_VR = "mountcksisoonvr"; public static final String MULTI_ARCH = "ismultiarch"; public static final String NSX_MODE = "nsxmode"; public static final String NETWORK_MODE = "networkmode"; @@ -342,12 +418,15 @@ public class ApiConstants { public static final String NETMASK = "netmask"; public static final String NEW_NAME = "newname"; public static final String NIC = "nic"; + public static final String NICS = "nics"; public static final String NIC_NETWORK_LIST = "nicnetworklist"; public static final String NIC_IP_ADDRESS_LIST = "nicipaddresslist"; public static final String NIC_MULTIQUEUE_NUMBER = "nicmultiqueuenumber"; public static final String NIC_PACKED_VIRTQUEUES_ENABLED = "nicpackedvirtqueuesenabled"; public static final String NEW_START_IP = "newstartip"; public static final String NEW_END_IP = "newendip"; + public static final String KUBERNETES_NODE_VERSION = "kubernetesnodeversion"; + public static final String NUMA_NODE = "numanode"; public static final String NUM_RETRIES = "numretries"; public static final String OFFER_HA = "offerha"; public static final String OS_DISTRIBUTION = "osdistribution"; @@ -364,8 +443,16 @@ public class ApiConstants { public static final String OS_TYPE_ID = "ostypeid"; public static final String OS_DISPLAY_NAME = "osdisplayname"; public static final String OS_NAME_FOR_HYPERVISOR = "osnameforhypervisor"; + public static final String GPU_CARD_ID = "gpucardid"; + public static final String GPU_CARD_NAME = "gpucardname"; + public static final String GPU_COUNT = "gpucount"; + public static final String GPU_DISPLAY = "gpudisplay"; + public static final String GPU_DEVICE_TYPE = "gpudevicetype"; + public static final String GPU_ENABLED = "gpuenabled"; + public static final String MAX_VGPU_PER_PHYSICAL_GPU = "maxvgpuperphysicalgpu"; public static final String GUEST_OS_LIST = "guestoslist"; public static final String GUEST_OS_COUNT = "guestoscount"; + public static final String GUEST_OS_RULE = "guestosrule"; public static final String OS_MAPPING_CHECK_ENABLED = "osmappingcheckenabled"; public static final String OUTOFBANDMANAGEMENT_POWERSTATE = "outofbandmanagementpowerstate"; public static final String OUTOFBANDMANAGEMENT_ENABLED = "outofbandmanagementenabled"; @@ -375,14 +462,17 @@ public class ApiConstants { public static final String PARENT = "parent"; public static final String PARENT_ID = "parentid"; public static final String PARENT_DOMAIN_ID = "parentdomainid"; + public static final String PARENT_GPU_DEVICE_ID = "parentgpudeviceid"; public static final String PARENT_SUBNET = "parentsubnet"; public static final String PARENT_TEMPLATE_ID = "parenttemplateid"; public static final String PASSWORD = "password"; + public static final String PCI_ROOT = "pciroot"; public static final String CURRENT_PASSWORD = "currentpassword"; public static final String SHOULD_UPDATE_PASSWORD = "update_passwd_on_host"; public static final String PASSWORD_ENABLED = "passwordenabled"; public static final String SSHKEY_ENABLED = "sshkeyenabled"; public static final String PATH = "path"; + public static final String PATH_READY = "pathready"; public static final String PAYLOAD = "payload"; public static final String PAYLOAD_URL = "payloadurl"; public static final String PEERS = "peers"; @@ -405,6 +495,8 @@ public class ApiConstants { public static final String POST_URL = "postURL"; public static final String POWER_STATE = "powerstate"; public static final String PRECEDENCE = "precedence"; + public static final String PREPARE_VM = "preparevm"; + public static final String PRESERVE_IP = "preserveip"; public static final String PRIVATE_INTERFACE = "privateinterface"; public static final String PRIVATE_IP = "privateip"; public static final String PRIVATE_PORT = "privateport"; @@ -421,11 +513,16 @@ public class ApiConstants { public static final String PUBLIC_END_PORT = "publicendport"; public static final String PUBLIC_ZONE = "publiczone"; public static final String PURGE_RESOURCES = "purgeresources"; + public static final String RAM = "RAM"; + public static final String REBALANCE = "rebalance"; public static final String RECEIVED_BYTES = "receivedbytes"; public static final String RECONNECT = "reconnect"; public static final String RECOVER = "recover"; public static final String REPAIR = "repair"; + public static final String REPETITION_ALLOWED = "repetitionallowed"; public static final String REQUIRES_HVM = "requireshvm"; + public static final String RESERVED_RESOURCE_DETAILS = "reservedresourcedetails"; + public static final String RESOURCES = "resources"; public static final String RESOURCE_COUNT = "resourcecount"; public static final String RESOURCE_NAME = "resourcename"; public static final String RESOURCE_TYPE = "resourcetype"; @@ -437,11 +534,13 @@ public class ApiConstants { public static final String REGISTERED = "registered"; public static final String QUALIFIERS = "qualifiers"; public static final String QUERY_FILTER = "queryfilter"; + public static final String QUIESCE_VM = "quiescevm"; + public static final String QUICK_RESTORE = "quickrestore"; public static final String SCHEDULE = "schedule"; + public static final String SCHEDULE_ID = "scheduleid"; public static final String SCOPE = "scope"; public static final String SEARCH_BASE = "searchbase"; public static final String SECONDARY_IP = "secondaryip"; - public static final String SECRET_KEY = "secretkey"; public static final String SECURITY_GROUP_IDS = "securitygroupids"; public static final String SECURITY_GROUP_NAMES = "securitygroupnames"; public static final String SECURITY_GROUP_NAME = "securitygroupname"; @@ -451,15 +550,19 @@ public class ApiConstants { public static final String SERIAL = "serial"; public static final String SERVICE_IP = "serviceip"; public static final String SERVICE_OFFERING_ID = "serviceofferingid"; + public static final String SERVICE_OFFERING_NAME = "serviceofferingname"; public static final String SESSIONKEY = "sessionkey"; public static final String SHOW_CAPACITIES = "showcapacities"; public static final String SHOW_REMOVED = "showremoved"; + public static final String SHOW_RESOURCES = "showresources"; public static final String SHOW_RESOURCE_ICON = "showicon"; public static final String SHOW_INACTIVE = "showinactive"; public static final String SHOW_UNIQUE = "showunique"; + public static final String SHOW_PERMISSIONS = "showpermissions"; public static final String SIGNATURE = "signature"; public static final String SIGNATURE_VERSION = "signatureversion"; public static final String SINCE = "since"; + public static final String SITE_NAME = "sitename"; public static final String SIZE = "size"; public static final String SIZEGB = "sizegb"; public static final String SNAPSHOT = "snapshot"; @@ -467,7 +570,11 @@ public class ApiConstants { public static final String SNAPSHOT_POLICY_ID = "snapshotpolicyid"; public static final String SNAPSHOT_TYPE = "snapshottype"; public static final String SNAPSHOT_QUIESCEVM = "quiescevm"; + + public static final String USE_STORAGE_REPLICATION = "usestoragereplication"; + public static final String SOURCE_CIDR_LIST = "sourcecidrlist"; + public static final String SOURCE_OFFERING_ID = "sourceofferingid"; public static final String SOURCE_ZONE_ID = "sourcezoneid"; public static final String SSL_VERIFICATION = "sslverification"; public static final String START_ASN = "startasn"; @@ -479,6 +586,8 @@ public class ApiConstants { public static final String STATE = "state"; public static final String STATS = "stats"; public static final String STATUS = "status"; + public static final String COMPRESSION_STATUS = "compressionstatus"; + public static final String VALIDATION_STATUS = "validationstatus"; public static final String STORAGE_TYPE = "storagetype"; public static final String STORAGE_POLICY = "storagepolicy"; public static final String STORAGE_MOTION_ENABLED = "storagemotionenabled"; @@ -490,10 +599,19 @@ public class ApiConstants { public static final String SYSTEM_VM_TYPE = "systemvmtype"; public static final String TAGS = "tags"; public static final String STORAGE_TAGS = "storagetags"; + public static final String STORAGE_ACCESS_GROUPS = "storageaccessgroups"; + public static final String STORAGE_ACCESS_GROUP = "storageaccessgroup"; + public static final String CLUSTER_STORAGE_ACCESS_GROUPS = "clusterstorageaccessgroups"; + public static final String POD_STORAGE_ACCESS_GROUPS = "podstorageaccessgroups"; + public static final String ZONE_STORAGE_ACCESS_GROUPS = "zonestorageaccessgroups"; public static final String SUCCESS = "success"; + public static final String SUCCESS_MESSAGE = "successmessage"; public static final String SUITABLE_FOR_VM = "suitableforvirtualmachine"; public static final String SUPPORTS_STORAGE_SNAPSHOT = "supportsstoragesnapshot"; public static final String TARGET_IQN = "targetiqn"; + public static final String TARIFF_ID = "tariffid"; + public static final String TARIFF_NAME = "tariffname"; + public static final String TASKS_FILTER = "tasksfilter"; public static final String TEMPLATE_FILTER = "templatefilter"; public static final String TEMPLATE_ID = "templateid"; public static final String TEMPLATE_IDS = "templateids"; @@ -503,13 +621,18 @@ public class ApiConstants { public static final String TIMEOUT = "timeout"; public static final String TIMEZONE = "timezone"; public static final String TIMEZONEOFFSET = "timezoneoffset"; + public static final String TENANT_NAME = "tenantname"; public static final String TOTAL = "total"; public static final String TOTAL_SUBNETS = "totalsubnets"; + public static final String TO_CHECKPOINT_ID = "tocheckpointid"; + public static final String TOTAL_QUOTA = "totalquota"; public static final String TYPE = "type"; public static final String TRUST_STORE = "truststore"; public static final String TRUST_STORE_PASSWORD = "truststorepass"; + public static final String UNIT = "unit"; public static final String URL = "url"; public static final String USAGE_INTERFACE = "usageinterface"; + public static final String USED = "used"; public static final String USED_SUBNETS = "usedsubnets"; public static final String USED_IOPS = "usediops"; public static final String USER_DATA = "userdata"; @@ -528,9 +651,18 @@ public class ApiConstants { public static final String USER_CONFIGURABLE = "userconfigurable"; public static final String USER_SECURITY_GROUP_LIST = "usersecuritygrouplist"; public static final String USER_SECRET_KEY = "usersecretkey"; + public static final String USE_VDDK = "usevddk"; public static final String USE_VIRTUAL_NETWORK = "usevirtualnetwork"; + public static final String USE_VIRTUAL_ROUTER_IP_RESOLVER = "userouteripresolver"; public static final String UPDATE_IN_SEQUENCE = "updateinsequence"; + public static final String VALIDATION_FORMAT = "validationformat"; public static final String VALUE = "value"; + public static final String VALUE_OPTIONS = "valueoptions"; + public static final String VENDOR_ID = "vendorid"; + public static final String VENDOR_NAME = "vendorname"; + public static final String VGPU_PROFILE_ID = "vgpuprofileid"; + public static final String VGPU_PROFILE_NAME = "vgpuprofilename"; + public static final String VIRTUAL_MACHINE = "virtualmachine"; public static final String VIRTUAL_MACHINE_ID = "virtualmachineid"; public static final String VIRTUAL_MACHINE_IDS = "virtualmachineids"; public static final String VIRTUAL_MACHINE_NAME = "virtualmachinename"; @@ -540,13 +672,21 @@ public class ApiConstants { public static final String VIRTUAL_MACHINE_STATE = "vmstate"; public static final String VIRTUAL_MACHINES = "virtualmachines"; public static final String USAGE_ID = "usageid"; + public static final String USAGE_NAME = "usagename"; public static final String USAGE_TYPE = "usagetype"; public static final String INCLUDE_TAGS = "includetags"; public static final String VLAN = "vlan"; public static final String VLAN_RANGE = "vlanrange"; + public static final String WORKER_SERVICE_OFFERING_ID = "workerofferingid"; + public static final String WORKER_SERVICE_OFFERING_NAME = "workerofferingname"; + public static final String CONTROL_SERVICE_OFFERING_ID = "controlofferingid"; + public static final String CONTROL_SERVICE_OFFERING_NAME = "controlofferingname"; + public static final String ETCD_SERVICE_OFFERING_ID = "etcdofferingid"; + public static final String ETCD_SERVICE_OFFERING_NAME = "etcdofferingname"; public static final String REMOVE_VLAN = "removevlan"; public static final String VLAN_ID = "vlanid"; + public static final String ISOLATED = "isolated"; public static final String ISOLATED_PVLAN = "isolatedpvlan"; public static final String ISOLATED_PVLAN_TYPE = "isolatedpvlantype"; public static final String ISOLATION_URI = "isolationuri"; @@ -554,8 +694,10 @@ public class ApiConstants { public static final String IS_DEDICATED = "isdedicated"; public static final String TAKEN = "taken"; public static final String VM_AVAILABLE = "vmavailable"; + public static final String VM_DETAILS = "vmdetails"; public static final String VM_LIMIT = "vmlimit"; public static final String VM_TOTAL = "vmtotal"; + public static final String VM_SETTINGS = "vmsettings"; public static final String VM_TYPE = "vmtype"; public static final String VNET = "vnet"; public static final String IS_VOLATILE = "isvolatile"; @@ -623,7 +765,7 @@ public class ApiConstants { public static final String NETWORK_DEVICE_PARAMETER_LIST = "networkdeviceparameterlist"; public static final String ZONE_TOKEN = "zonetoken"; public static final String DHCP_PROVIDER = "dhcpprovider"; - public static final String RESULT = "success"; + public static final String RESULT = "result"; public static final String RESUME = "resume"; public static final String LUN_ID = "lunId"; public static final String IQN = "iqn"; @@ -653,6 +795,7 @@ public class ApiConstants { public static final String ROLE_TYPE = "roletype"; public static final String ROLE_NAME = "rolename"; public static final String PERMISSION = "permission"; + public static final String PERMISSIONS = "permissions"; public static final String RULE = "rule"; public static final String RULES = "rules"; public static final String RULE_ID = "ruleid"; @@ -737,6 +880,7 @@ public class ApiConstants { public static final String LIST_ALL = "listall"; public static final String LIST_ONLY_REMOVED = "listonlyremoved"; public static final String LIST_SYSTEM_VMS = "listsystemvms"; + public static final String LIST_VM_DETAILS = "listvmdetails"; public static final String IP_RANGES = "ipranges"; public static final String IPV4_ROUTING = "ip4routing"; public static final String IPV4_ROUTES = "ip4routes"; @@ -746,9 +890,17 @@ public class ApiConstants { public static final String IS_SOURCE_NAT = "issourcenat"; public static final String IS_STATIC_NAT = "isstaticnat"; public static final String ITERATIONS = "iterations"; + public static final String ITEMS = "items"; public static final String SORT_BY = "sortby"; public static final String CHANGE_CIDR = "changecidr"; + public static final String HSM_PROFILE = "hsmprofile"; + public static final String HSM_PROFILE_ID = "hsmprofileid"; public static final String PURPOSE = "purpose"; + public static final String KMS_KEY = "kmskey"; + public static final String KMS_KEY_ID = "kmskeyid"; + public static final String KMS_KEY_VERSION = "kmskeyversion"; + public static final String KEK_LABEL = "keklabel"; + public static final String KEY_BITS = "keybits"; public static final String IS_TAGGED = "istagged"; public static final String INSTANCE_NAME = "instancename"; public static final String CONSIDER_LAST_HOST = "considerlasthost"; @@ -869,9 +1021,12 @@ public class ApiConstants { public static final String REGION_ID = "regionid"; public static final String VPC_OFF_ID = "vpcofferingid"; public static final String VPC_OFF_NAME = "vpcofferingname"; + public static final String VPC_OFFERING_CONSERVE_MODE = "vpcofferingconservemode"; public static final String NETWORK = "network"; public static final String VPC_ID = "vpcid"; public static final String VPC_NAME = "vpcname"; + public static final String VPC_GATEWAY_ID = "vpcgatewayid"; + public static final String VPC_GATEWAY_IP = "vpcgatewayip"; public static final String GATEWAY_ID = "gatewayid"; public static final String CAN_USE_FOR_DEPLOY = "canusefordeploy"; public static final String RESOURCE_IDS = "resourceids"; @@ -895,6 +1050,7 @@ public class ApiConstants { public static final String SPLIT_CONNECTIONS = "splitconnections"; public static final String FOR_VPC = "forvpc"; public static final String FOR_NSX = "fornsx"; + public static final String FOR_CKS = "forcks"; public static final String NSX_SUPPORT_LB = "nsxsupportlb"; public static final String NSX_SUPPORTS_INTERNAL_LB = "nsxsupportsinternallb"; public static final String FOR_TUNGSTEN = "fortungsten"; @@ -912,7 +1068,7 @@ public class ApiConstants { public static final String NSX_PROVIDER_PORT = "nsxproviderport"; public static final String NSX_CONTROLLER_ID = "nsxcontrollerid"; public static final String S3_ACCESS_KEY = "accesskey"; - public static final String S3_SECRET_KEY = "secretkey"; + public static final String SECRET_KEY = "secretkey"; public static final String S3_END_POINT = "endpoint"; public static final String S3_BUCKET_NAME = "bucket"; public static final String S3_SIGNER = "s3signer"; @@ -1014,6 +1170,7 @@ public class ApiConstants { public static final String RESOURCE_DETAILS = "resourcedetails"; public static final String RESOURCE_ICON = "icon"; + public static final String RESOURCE_MAP = "resourcemap"; public static final String EXPUNGE = "expunge"; public static final String FOR_DISPLAY = "fordisplay"; public static final String PASSIVE = "passive"; @@ -1045,17 +1202,23 @@ public class ApiConstants { public static final String NETWORK_SPANNED_ZONES = "zonesnetworkspans"; public static final String METADATA = "metadata"; public static final String PHYSICAL_SIZE = "physicalsize"; + public static final String CHAIN_SIZE = "chainsize"; public static final String OVM3_POOL = "ovm3pool"; public static final String OVM3_CLUSTER = "ovm3cluster"; public static final String OVM3_VIP = "ovm3vip"; public static final String CLEAN_UP_DETAILS = "cleanupdetails"; + public static final String CLEAN_UP_EXTERNAL_DETAILS = "cleanupexternaldetails"; + public static final String CLEAN_UP_EXTRA_CONFIG = "cleanupextraconfig"; + public static final String CLEAN_UP_PARAMETERS = "cleanupparameters"; public static final String VIRTUAL_SIZE = "virtualsize"; + public static final String UNCOMPRESSED_SIZE = "uncompressedsize"; public static final String NETSCALER_CONTROLCENTER_ID = "netscalercontrolcenterid"; public static final String NETSCALER_SERVICEPACKAGE_ID = "netscalerservicepackageid"; public static final String FETCH_ROUTER_HEALTH_CHECK_RESULTS = "fetchhealthcheckresults"; public static final String ZONE_ID_LIST = "zoneids"; public static final String DESTINATION_ZONE_ID_LIST = "destzoneids"; + public static final String STORAGE_ID_LIST = "storageids"; public static final String ADMIN = "admin"; public static final String CHECKSUM_PARAMETER_PREFIX_DESCRIPTION = "The parameter containing the checksum will be considered a MD5sum if it is not prefixed\n" + " and just a plain ascii/utf8 representation of a hexadecimal string. If it is required to\n" @@ -1104,6 +1267,10 @@ public class ApiConstants { public static final String MASTER_NODES = "masternodes"; public static final String NODE_IDS = "nodeids"; public static final String CONTROL_NODES = "controlnodes"; + public static final String ETCD_NODES = "etcdnodes"; + public static final String EXTERNAL_NODES = "externalnodes"; + public static final String IS_EXTERNAL_NODE = "isexternalnode"; + public static final String IS_ETCD_NODE = "isetcdnode"; public static final String MIN_SEMANTIC_VERSION = "minimumsemanticversion"; public static final String MIN_KUBERNETES_VERSION_ID = "minimumkubernetesversionid"; public static final String NODE_ROOT_DISK_SIZE = "noderootdisksize"; @@ -1112,6 +1279,15 @@ public class ApiConstants { public static final String AUTOSCALING_ENABLED = "autoscalingenabled"; public static final String MIN_SIZE = "minsize"; public static final String MAX_SIZE = "maxsize"; + public static final String NODE_TYPE_OFFERING_MAP = "nodeofferings"; + public static final String NODE_TYPE_TEMPLATE_MAP = "nodetemplates"; + public static final String NODE_TYPE_AFFINITY_GROUP_MAP = "nodeaffinitygroups"; + public static final String CONTROL_AFFINITY_GROUP_IDS = "controlaffinitygroupids"; + public static final String CONTROL_AFFINITY_GROUP_NAMES = "controlaffinitygroupnames"; + public static final String WORKER_AFFINITY_GROUP_IDS = "workeraffinitygroupids"; + public static final String WORKER_AFFINITY_GROUP_NAMES = "workeraffinitygroupnames"; + public static final String ETCD_AFFINITY_GROUP_IDS = "etcdaffinitygroupids"; + public static final String ETCD_AFFINITY_GROUP_NAMES = "etcdaffinitygroupnames"; public static final String BOOT_TYPE = "boottype"; public static final String BOOT_MODE = "bootmode"; @@ -1133,37 +1309,46 @@ public class ApiConstants { public static final String PROVIDER_FOR_2FA = "providerfor2fa"; public static final String ISSUER_FOR_2FA = "issuerfor2fa"; public static final String MANDATE_2FA = "mandate2fa"; + public static final String PASSWORD_CHANGE_REQUIRED = "passwordchangerequired"; public static final String SECRET_CODE = "secretcode"; public static final String LOGIN = "login"; public static final String LOGOUT = "logout"; public static final String LIST_IDPS = "listIdps"; - public static final String READY_FOR_SHUTDOWN = "readyforshutdown"; + public static final String MAINTENANCE_INITIATED = "maintenanceinitiated"; public static final String SHUTDOWN_TRIGGERED = "shutdowntriggered"; + public static final String READY_FOR_SHUTDOWN = "readyforshutdown"; public static final String PENDING_JOBS_COUNT = "pendingjobscount"; + public static final String AGENTS_COUNT = "agentscount"; + public static final String AGENTS = "agents"; + public static final String LAST_AGENTS = "lastagents"; public static final String PUBLIC_MTU = "publicmtu"; public static final String PRIVATE_MTU = "privatemtu"; public static final String MTU = "mtu"; public static final String AUTO_ENABLE_KVM_HOST = "autoenablekvmhost"; public static final String LIST_APIS = "listApis"; - public static final String OBJECT_STORAGE_ID = "objectstorageid"; public static final String VERSIONING = "versioning"; public static final String OBJECT_LOCKING = "objectlocking"; public static final String ENCRYPTION = "encryption"; public static final String QUOTA = "quota"; + public static final String QUOTA_CONSUMED = "quotaconsumed"; + public static final String QUOTA_USAGE = "quotausage"; public static final String ACCESS_KEY = "accesskey"; public static final String SOURCE_NAT_IP = "sourcenatipaddress"; public static final String SOURCE_NAT_IP_ID = "sourcenatipaddressid"; public static final String HAS_RULES = "hasrules"; public static final String NSX_DETAIL_KEY = "forNsx"; + public static final String NETRIS_DETAIL_KEY = "forNetris"; + public static final String NETRIS_TAG = "netristag"; + public static final String NETRIS_VXLAN_ID = "netrisvxlanid"; + public static final String NETRIS_URL = "netrisurl"; public static final String DISK_PATH = "diskpath"; public static final String IMPORT_SOURCE = "importsource"; public static final String TEMP_PATH = "temppath"; - public static final String OBJECT_STORAGE = "objectstore"; public static final String HEURISTIC_RULE = "heuristicrule"; - public static final String HEURISTIC_TYPE_VALID_OPTIONS = "Valid options are: ISO, SNAPSHOT, TEMPLATE and VOLUME."; + public static final String HEURISTIC_TYPE_VALID_OPTIONS = "Valid options are: ISO, SNAPSHOT, BACKUP, TEMPLATE and VOLUME."; public static final String MANAGEMENT = "management"; public static final String IS_VNF = "isvnf"; public static final String VNF_NICS = "vnfnics"; @@ -1173,8 +1358,10 @@ public class ApiConstants { public static final String VNF_CONFIGURE_MANAGEMENT = "vnfconfiguremanagement"; public static final String VNF_CIDR_LIST = "vnfcidrlist"; + public static final String AUTHORIZE_URL = "authorizeurl"; public static final String CLIENT_ID = "clientid"; public static final String REDIRECT_URI = "redirecturi"; + public static final String TOKEN_URL = "tokenurl"; public static final String IS_TAG_A_RULE = "istagarule"; @@ -1189,12 +1376,120 @@ public class ApiConstants { public static final String SHAREDFSVM_MIN_CPU_COUNT = "sharedfsvmmincpucount"; public static final String SHAREDFSVM_MIN_RAM_SIZE = "sharedfsvmminramsize"; + // Object Storage related + public static final String BUCKET_AVAILABLE = "bucketavailable"; + public static final String BUCKET_LIMIT = "bucketlimit"; + public static final String BUCKET_TOTAL = "buckettotal"; + public static final String OBJECT_STORAGE_ID = "objectstorageid"; + public static final String OBJECT_STORAGE = "objectstore"; + public static final String OBJECT_STORAGE_AVAILABLE = "objectstorageavailable"; + 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."; + + // DNS provider related + public static final String NAME_SERVERS = "nameservers"; + public static final String DNS_USER_NAME = "dnsusername"; + public static final String DNS_API_KEY = "dnsapikey"; + public static final String DNS_ZONE_ID = "dnszoneid"; + public static final String DNS_ZONE = "dnszone"; + public static final String DNS_RECORD = "dnsrecord"; + public static final String DNS_SUB_DOMAIN = "dnssubdomain"; + public static final String DNS_SERVER_ID = "dnsserverid"; + public static final String CONTENT = "content"; + public static final String CONTENTS = "contents"; + public static final String PUBLIC_DOMAIN_SUFFIX = "publicdomainsuffix"; + public static final String AUTHORITATIVE = "authoritative"; + public static final String KIND = "kind"; + public static final String DNS_SEC = "dnssec"; + public static final String TTL = "ttl"; + public static final String CHANGE_TYPE = "changetype"; + public static final String RECORDS = "records"; + public static final String RR_SETS = "rrsets"; + public static final String X_API_KEY = "X-API-Key"; + public static final String DISABLED = "disabled"; + public static final String CONTENT_TYPE = "Content-Type"; + public static final String NATIVE_ZONE = "Native"; + public static final String NIC_DNS_NAME = "nicdnsname"; + public static final String TIME_STAMP = "timestamp"; + public static final String INSTANCE_ID = "instanceId"; + public static final String OLD_STATE = "oldState"; + public static final String NEW_STATE = "newState"; + public static final String OLD_HOST_NAME = "oldHostName"; + public static final String EXISTING = "existing"; + public static final String UNMANAGE = "unmanage"; + 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 " + "value will be applied."; + + public static final String PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " + + "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " + + "added, it will be interpreted as \"00:00:00\"). If the recommended format is not used, the date will be considered in the server timezone."; + + public static final String PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " + + "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " + + "added, it will be interpreted as \"23:59:59\"). If the recommended format is not used, the date will be considered in the server timezone."; + + public static final String PARAMETER_DESCRIPTION_MAX_BACKUPS = "The maximum number of backups to keep for a VM. " + + "If \"0\", no retention policy will be applied and, thus, no backups from the schedule will be automatically deleted. " + + "This parameter is only supported for the Dummy, NAS and EMC Networker backup provider."; + public static final String VMWARE_DC = "vmwaredc"; + public static final String PARAMETER_DESCRIPTION_ISOLATED_BACKUPS = "Whether the backup will be isolated, defaults to false. " + + "Isolated backups are always created as full backups in independent chains. Therefore, they will never depend on any existing backup chain " + + "and no backup chain will depend on them. Currently only supported for the KBOSS provider."; + + public static final String CSS = "css"; + + public static final String JSON_CONFIGURATION = "jsonconfiguration"; + public static final String LOGIN_BASE_DOMAIN = "loginbasedomain"; + + public static final String COMMON_NAMES = "commonnames"; + + public static final String COMMON_NAME = "commonname"; + + public static final String DOMAIN_IDS = "domainids"; + + public static final String SHOW_PUBLIC = "showpublic"; + + public static final String LIST_ONLY_DEFAULT_THEME = "listonlydefaulttheme"; + + public static final String RECURSIVE_DOMAINS = "recursivedomains"; + + public static final String VPN_CUSTOMER_GATEWAY_PARAMETERS = "vpncustomergatewayparameters"; + public static final String OBSOLETE_PARAMETERS = "obsoleteparameters"; + public static final String EXCLUDED_PARAMETERS = "excludedparameters"; + + public static final String COMPRESS = "compress"; + + public static final String VALIDATE = "validate"; + + public static final String VALIDATION_STEPS = "validationsteps"; + + public static final String ALLOW_QUICK_RESTORE = "allowquickrestore"; + + public static final String ALLOW_EXTRACT_FILE = "allowextractfile"; + + public static final String BACKUP_CHAIN_SIZE = "backupchainsize"; + + public static final String COMPRESSION_LIBRARY = "compressionlibrary"; + public static final String ATTEMPTS = "attempts"; + + public static final String EXECUTING = "executing"; + + public static final String SCHEDULED = "scheduled"; + public static final String SCHEDULED_DATE = "scheduleddate"; + public static final String BACKUP_PROVIDER = "backupprovider"; + /** * This enum specifies IO Drivers, each option controls specific policies on I/O. * Qemu guests support "threads" and "native" options Since 0.8.8 ; "io_uring" is supported Since 6.3.0 (QEMU 5.0). @@ -1217,14 +1512,6 @@ public String toString() { } } - public static final String PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " + - "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " + - "added, it will be interpreted as \"00:00:00\"). If the recommended format is not used, the date will be considered in the server timezone."; - - public static final String PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS = "The recommended format is \"yyyy-MM-dd'T'HH:mm:ssZ\" (e.g.: \"2023-01-01T12:00:00+0100\"); " + - "however, the following formats are also accepted: \"yyyy-MM-dd HH:mm:ss\" (e.g.: \"2023-01-01 12:00:00\") and \"yyyy-MM-dd\" (e.g.: \"2023-01-01\" - if the time is not " + - "added, it will be interpreted as \"23:59:59\"). If the recommended format is not used, the date will be considered in the server timezone."; - public enum BootType { UEFI, BIOS; @@ -1244,7 +1531,7 @@ public String toString() { } public enum HostDetails { - all, capacity, events, stats, min; + all, capacity, core, events, stats, min; } public enum VMDetails { @@ -1255,6 +1542,10 @@ public enum DomainDetails { all, resource, min; } + public enum ExtensionDetails { + all, resource, external, min; + } + public enum ApiKeyAccess { DISABLED(false), ENABLED(true), diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java b/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java index d4fdeddc9a95..616c37484d87 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiErrorCode.java @@ -22,6 +22,7 @@ */ public enum ApiErrorCode { + BAD_REQUEST(400), UNAUTHORIZED(401), UNAUTHORIZED2FA(511), METHOD_NOT_ALLOWED(405), @@ -30,6 +31,7 @@ public enum ApiErrorCode { UNSUPPORTED_ACTION_ERROR(432), API_LIMIT_EXCEED(429), + SERVICE_UNAVAILABLE(503), INTERNAL_ERROR(530), ACCOUNT_ERROR(531), ACCOUNT_RESOURCE_LIMIT_ERROR(532), diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java index a422129ad213..1ee41ac86c22 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java +++ b/api/src/main/java/org/apache/cloudstack/api/ApiServerService.java @@ -21,8 +21,11 @@ import javax.servlet.http.HttpSession; +import org.apache.cloudstack.context.CallContext; + import com.cloud.domain.Domain; import com.cloud.exception.CloudAuthenticationException; +import com.cloud.user.Account; import com.cloud.user.UserAccount; public interface ApiServerService { @@ -50,4 +53,22 @@ public ResponseObject loginUser(HttpSession session, String username, String pas boolean resetPassword(UserAccount userAccount, String token, String password); String getDomainId(Map params); + + boolean isPostRequestsAndTimestampsEnforced(); + + AsyncCmdResult processAsyncCmd(BaseAsyncCmd cmdObj, Map params, CallContext ctx, Long callerUserId, Account caller) throws Exception; + + class AsyncCmdResult { + public final Long objectId; + public final String objectUuid; + public final BaseAsyncCmd asyncCmd; + public final long jobId; + + public AsyncCmdResult(Long objectId, String objectUuid, BaseAsyncCmd asyncCmd, long jobId) { + this.objectId = objectId; + this.objectUuid = objectUuid; + this.asyncCmd = asyncCmd; + this.jobId = jobId; + } + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCmd.java index 6859b0a7f406..c67c5a023e09 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCmd.java @@ -29,6 +29,7 @@ public abstract class BaseAsyncCmd extends BaseCmd { public static final String migrationSyncObject = "migration"; public static final String snapshotHostSyncObject = "snapshothost"; public static final String gslbSyncObject = "globalserverloadbalancer"; + public static final String user = "user"; private Object job; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCreateCustomIdCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCreateCustomIdCmd.java index 30baa71d6d85..b9b13dcfd884 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCreateCustomIdCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseAsyncCreateCustomIdCmd.java @@ -20,7 +20,7 @@ public abstract class BaseAsyncCreateCustomIdCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.CUSTOM_ID, type = CommandType.STRING, description = "An optional field, in case you want to set a custom id to the resource. Allowed to Root Admins only") - private String customId; + protected String customId; public String getCustomId() { return customId; diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java index 0aa8366bcd5c..2a64a1fb6fd8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseBackupListCmd.java @@ -25,7 +25,7 @@ import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.context.CallContext; -public abstract class BaseBackupListCmd extends BaseListCmd { +public abstract class BaseBackupListCmd extends BaseListAccountResourcesCmd { protected void setupResponseBackupOfferingsList(final List offerings, final Integer count) { final ListResponse response = new ListResponse<>(); diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java index 457afdc88478..483fa83630ad 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseCmd.java @@ -35,10 +35,13 @@ import org.apache.cloudstack.acl.ProjectRoleService; import org.apache.cloudstack.acl.RoleService; import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPairService; import org.apache.cloudstack.affinity.AffinityGroupService; import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dns.DnsProviderManager; +import org.apache.cloudstack.gpu.GpuService; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerService; import org.apache.cloudstack.network.lb.InternalLoadBalancerVMService; @@ -94,6 +97,7 @@ import com.cloud.utils.db.EntityManager; import com.cloud.utils.db.UUIDManager; import com.cloud.vm.UserVmService; +import com.cloud.vm.VmDetailConstants; import com.cloud.vm.snapshot.VMSnapshotService; public abstract class BaseCmd { @@ -130,6 +134,8 @@ public static enum CommandType { @Inject public UserVmService _userVmService; @Inject + public GpuService gpuService; + @Inject public ManagementService _mgr; @Inject public StorageService _storageService; @@ -216,6 +222,8 @@ public static enum CommandType { @Inject public Ipv6Service ipv6Service; @Inject + public ApiKeyPairService apiKeyPairService; + @Inject public VnfTemplateManager vnfTemplateManager; @Inject public BucketApiService _bucketService; @@ -225,6 +233,9 @@ public static enum CommandType { @Inject public RoutedIpv4Manager routedIpv4Manager; + @Inject + public DnsProviderManager dnsProviderManager; + public abstract void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException; @@ -378,7 +389,7 @@ public List getParamFields() { if (roleIsAllowed) { validFields.add(field); } else { - logger.debug("Ignoring parameter " + parameterAnnotation.name() + " as the caller is not authorized to pass it in"); + logger.debug("Ignoring parameter {} as the caller is not authorized to pass it in", parameterAnnotation.name()); } } @@ -484,4 +495,18 @@ public Map convertDetailsToMap(Map details) { } return detailsMap; } + + public Map convertExternalDetailsToMap(Map externalDetails) { + Map customparameterMap = convertDetailsToMap(externalDetails); + Map details = new HashMap<>(); + for (String key : customparameterMap.keySet()) { + String value = customparameterMap.get(key); + details.put(VmDetailConstants.EXTERNAL_DETAIL_PREFIX + key, value); + } + return details; + } + + public String getResourceUuid(String parameterName) { + return CallContext.current().getApiResourceUuid(parameterName); + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java index 54a398d756f1..94c5d8ff39fc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/BaseUpdateTemplateOrIsoCmd.java @@ -51,6 +51,10 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd { description = "The ID of the OS type that best represents the OS of this image.") private Long osTypeId; + @Parameter(name = ApiConstants.FORCE_UPDATE_OS_TYPE, type = CommandType.BOOLEAN, since = "4.21", description = "Force OS type update. Warning: Updating OS type will " + + "update the guest OS configuration for all the existing Instances deployed with this template/iso, which may affect their behavior.") + private Boolean forceUpdateOsType; + @Parameter(name = ApiConstants.FORMAT, type = CommandType.STRING, description = "The format for the image") private String format; @@ -80,7 +84,7 @@ public abstract class BaseUpdateTemplateOrIsoCmd extends BaseCmd { private Boolean cleanupDetails; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template/ISO. Valid options are: x86_64, aarch64", + description = "the CPU arch of the template/ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; @@ -112,6 +116,10 @@ public Long getOsTypeId() { return osTypeId; } + public Boolean getForceUpdateOsType() { + return forceUpdateOsType; + } + public Boolean getPasswordEnabled() { return passwordEnabled; } diff --git a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java index ea0d946ee417..6e880c89432f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java +++ b/api/src/main/java/org/apache/cloudstack/api/ResponseGenerator.java @@ -22,28 +22,28 @@ import java.util.Map; import java.util.Set; -import com.cloud.bgp.ASNumber; -import com.cloud.bgp.ASNumberRange; - -import org.apache.cloudstack.storage.object.Bucket; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.consoleproxy.ConsoleSession; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; import org.apache.cloudstack.affinity.AffinityGroup; import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ApiConstants.HostDetails; import org.apache.cloudstack.api.ApiConstants.VMDetails; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.user.job.QueryAsyncJobResultCmd; -import org.apache.cloudstack.api.response.AccountResponse; -import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.ASNRangeResponse; import org.apache.cloudstack.api.response.ASNumberResponse; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.ApplicationLoadBalancerResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; import org.apache.cloudstack.api.response.AutoScalePolicyResponse; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.AutoScaleVmProfileResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.BackupRepositoryResponse; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.api.response.BaseRolePermissionResponse; import org.apache.cloudstack.api.response.BucketResponse; import org.apache.cloudstack.api.response.CapacityResponse; import org.apache.cloudstack.api.response.ClusterResponse; @@ -60,25 +60,27 @@ import org.apache.cloudstack.api.response.DomainRouterResponse; import org.apache.cloudstack.api.response.EventResponse; import org.apache.cloudstack.api.response.ExtractResponse; -import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.FirewallResponse; import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.GlobalLoadBalancerResponse; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.GuestOsMappingResponse; import org.apache.cloudstack.api.response.GuestVlanRangeResponse; import org.apache.cloudstack.api.response.GuestVlanResponse; +import org.apache.cloudstack.api.response.GuiThemeResponse; import org.apache.cloudstack.api.response.HostForMigrationResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.HypervisorCapabilitiesResponse; import org.apache.cloudstack.api.response.HypervisorGuestOsNamesResponse; import org.apache.cloudstack.api.response.IPAddressResponse; -import org.apache.cloudstack.api.response.IpQuarantineResponse; import org.apache.cloudstack.api.response.ImageStoreResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse; import org.apache.cloudstack.api.response.InternalLoadBalancerElementResponse; import org.apache.cloudstack.api.response.IpForwardingRuleResponse; +import org.apache.cloudstack.api.response.IpQuarantineResponse; import org.apache.cloudstack.api.response.IsolationMethodResponse; +import org.apache.cloudstack.api.response.ApiKeyPairResponse; import org.apache.cloudstack.api.response.LBHealthCheckResponse; import org.apache.cloudstack.api.response.LBStickinessResponse; import org.apache.cloudstack.api.response.ListResponse; @@ -115,6 +117,7 @@ import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.ServiceResponse; +import org.apache.cloudstack.api.response.SharedFSResponse; import org.apache.cloudstack.api.response.Site2SiteCustomerGatewayResponse; import org.apache.cloudstack.api.response.Site2SiteVpnConnectionResponse; import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; @@ -144,7 +147,6 @@ import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.backup.BackupRepository; import org.apache.cloudstack.backup.BackupSchedule; @@ -153,16 +155,21 @@ import org.apache.cloudstack.direct.download.DirectDownloadCertificate; import org.apache.cloudstack.direct.download.DirectDownloadCertificateHostMap; import org.apache.cloudstack.direct.download.DirectDownloadManager; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.network.lb.ApplicationLoadBalancerRule; import org.apache.cloudstack.region.PortableIp; import org.apache.cloudstack.region.PortableIpRange; import org.apache.cloudstack.region.Region; import org.apache.cloudstack.secstorage.heuristics.Heuristic; -import org.apache.cloudstack.storage.sharedfs.SharedFS; +import org.apache.cloudstack.storage.object.Bucket; import org.apache.cloudstack.storage.object.ObjectStore; +import org.apache.cloudstack.storage.sharedfs.SharedFS; import org.apache.cloudstack.usage.Usage; +import org.apache.cloudstack.vm.UnmanagedInstanceTO; +import com.cloud.bgp.ASNumber; +import com.cloud.bgp.ASNumberRange; import com.cloud.capacity.Capacity; import com.cloud.configuration.ResourceCount; import com.cloud.configuration.ResourceLimit; @@ -223,10 +230,11 @@ import com.cloud.projects.ProjectInvitation; import com.cloud.region.ha.GlobalLoadBalancerRule; import com.cloud.resource.RollingMaintenanceManager; -import com.cloud.server.ResourceTag; import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceTag; import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSHypervisor; +import com.cloud.storage.GuestOsCategory; import com.cloud.storage.ImageStore; import com.cloud.storage.Snapshot; import com.cloud.storage.StoragePool; @@ -240,14 +248,13 @@ import com.cloud.user.UserAccount; import com.cloud.user.UserData; import com.cloud.uservm.UserVm; -import com.cloud.utils.net.Ip; import com.cloud.utils.Pair; +import com.cloud.utils.net.Ip; import com.cloud.vm.InstanceGroup; import com.cloud.vm.Nic; import com.cloud.vm.NicSecondaryIp; import com.cloud.vm.VirtualMachine; import com.cloud.vm.snapshot.VMSnapshot; -import org.apache.cloudstack.vm.UnmanagedInstanceTO; public interface ResponseGenerator { UserResponse createUserResponse(UserAccount user); @@ -274,7 +281,8 @@ public interface ResponseGenerator { List createUserVmResponse(ResponseView view, String objectName, UserVm... userVms); - List createUserVmResponse(ResponseView view, String objectName, EnumSet details, UserVm... userVms); + List createUserVmResponse(ResponseView view, String objectName, EnumSet details, + UserVm... userVms); SystemVmResponse createSystemVmResponse(VirtualMachine systemVM); @@ -300,17 +308,22 @@ public interface ResponseGenerator { LoadBalancerResponse createLoadBalancerResponse(LoadBalancer loadBalancer); - LBStickinessResponse createLBStickinessPolicyResponse(List stickinessPolicies, LoadBalancer lb); + LBStickinessResponse createLBStickinessPolicyResponse(List stickinessPolicies, + LoadBalancer lb); LBStickinessResponse createLBStickinessPolicyResponse(StickinessPolicy stickinessPolicy, LoadBalancer lb); - LBHealthCheckResponse createLBHealthCheckPolicyResponse(List healthcheckPolicies, LoadBalancer lb); + LBHealthCheckResponse createLBHealthCheckPolicyResponse(List healthcheckPolicies, + LoadBalancer lb); LBHealthCheckResponse createLBHealthCheckPolicyResponse(HealthCheckPolicy healthcheckPolicy, LoadBalancer lb); PodResponse createPodResponse(Pod pod, Boolean showCapacities); - ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, Boolean showResourceIcon); + PodResponse createMinimalPodResponse(Pod pod); + + ZoneResponse createZoneResponse(ResponseView view, DataCenter dataCenter, Boolean showCapacities, + Boolean showResourceIcon); DataCenterGuestIpv6PrefixResponse createDataCenterGuestIpv6PrefixResponse(DataCenterGuestIpv6Prefix prefix); @@ -324,6 +337,8 @@ public interface ResponseGenerator { ClusterResponse createClusterResponse(Cluster cluster, Boolean showCapacities); + ClusterResponse createMinimalClusterResponse(Cluster cluster); + FirewallRuleResponse createPortForwardingRuleResponse(PortForwardingRule fwRule); IpForwardingRuleResponse createIpForwardingRuleResponse(StaticNatRule fwRule); @@ -332,6 +347,8 @@ public interface ResponseGenerator { UserVm findUserVmById(Long vmId); + UserVm findUserVmByNicId(Long nicId); + Volume findVolumeById(Long volumeId); Account findAccountByNameDomain(String accountName, Long domainId); @@ -348,7 +365,8 @@ public interface ResponseGenerator { List createTemplateResponses(ResponseView view, long templateId, Long zoneId, boolean readyOnly); - List createTemplateResponses(ResponseView view, long templateId, Long snapshotId, Long volumeId, boolean readyOnly); + List createTemplateResponses(ResponseView view, long templateId, Long snapshotId, Long volumeId, + boolean readyOnly); SecurityGroupResponse createSecurityGroupResponseFromSecurityGroupRule(List securityRules); @@ -367,14 +385,15 @@ public interface ResponseGenerator { TemplateResponse createTemplateUpdateResponse(ResponseView view, VirtualMachineTemplate result); List createTemplateResponses(ResponseView view, VirtualMachineTemplate result, - Long zoneId, boolean readyOnly); + Long zoneId, boolean readyOnly); List createTemplateResponses(ResponseView view, VirtualMachineTemplate result, - List zoneIds, boolean readyOnly); + List zoneIds, boolean readyOnly); List createCapacityResponse(List result, DecimalFormat format); - TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseView view, List accountNames, Long id); + TemplatePermissionsResponse createTemplatePermissionsResponse(ResponseView view, List accountNames, + Long id); AsyncJobResponse queryJobResult(QueryAsyncJobResultCmd cmd); @@ -388,7 +407,8 @@ List createTemplateResponses(ResponseView view, VirtualMachine Long getSecurityGroupId(String groupName, long accountId); - List createIsoResponses(ResponseView view, VirtualMachineTemplate iso, Long zoneId, boolean readyOnly); + List createIsoResponses(ResponseView view, VirtualMachineTemplate iso, Long zoneId, + boolean readyOnly); ProjectResponse createProjectResponse(Project project); @@ -481,17 +501,23 @@ List createTemplateResponses(ResponseView view, VirtualMachine AutoScaleVmGroupResponse createAutoScaleVmGroupResponse(AutoScaleVmGroup vmGroup); + GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory); + + GuestOSCategoryResponse createGuestOSCategoryResponse(GuestOsCategory guestOsCategory, boolean showIcon); + GuestOSResponse createGuestOSResponse(GuestOS os); GuestOsMappingResponse createGuestOSMappingResponse(GuestOSHypervisor osHypervisor); - HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse(List> hypervisorGuestOsNames); + HypervisorGuestOsNamesResponse createHypervisorGuestOSNamesResponse( + List> hypervisorGuestOsNames); SnapshotScheduleResponse createSnapshotScheduleResponse(SnapshotSchedule sched); UsageRecordResponse createUsageResponse(Usage usageRecord); - UsageRecordResponse createUsageResponse(Usage usageRecord, Map> resourceTagResponseMap, boolean oldFormat); + UsageRecordResponse createUsageResponse(Usage usageRecord, + Map> resourceTagResponseMap, boolean oldFormat); public Map> getUsageResourceTags(); @@ -503,7 +529,8 @@ List createTemplateResponses(ResponseView view, VirtualMachine public NicResponse createNicResponse(Nic result); - ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb, Map lbInstances); + ApplicationLoadBalancerResponse createLoadBalancerContainerReponse(ApplicationLoadBalancerRule lb, + Map lbInstances); AffinityGroupResponse createAffinityGroupResponse(AffinityGroup group); @@ -523,17 +550,18 @@ List createTemplateResponses(ResponseView view, VirtualMachine UserDataResponse createUserDataResponse(UserData userData); - BackupResponse createBackupResponse(Backup backup); - BackupScheduleResponse createBackupScheduleResponse(BackupSchedule backup); BackupOfferingResponse createBackupOfferingResponse(BackupOffering policy); ManagementServerResponse createManagementResponse(ManagementServerHost mgmt); - List createHealthCheckResponse(VirtualMachine router, List healthCheckResults); + List createHealthCheckResponse(VirtualMachine router, + List healthCheckResults); - RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, List hostsUpdated, List hostsSkipped); + RollingMaintenanceResponse createRollingMaintenanceResponse(Boolean success, String details, + List hostsUpdated, + List hostsSkipped); ResourceIconResponse createResourceIconResponse(ResourceIcon resourceIcon); @@ -543,11 +571,14 @@ List createTemplateResponses(ResponseView view, VirtualMachine DirectDownloadCertificateResponse createDirectDownloadCertificateResponse(DirectDownloadCertificate certificate); - List createDirectDownloadCertificateHostMapResponse(List hostMappings); + List createDirectDownloadCertificateHostMapResponse( + List hostMappings); - DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse(DirectDownloadManager.HostCertificateStatus status); + DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateHostStatusResponse( + DirectDownloadManager.HostCertificateStatus status); - DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, Long hostId, Pair result); + DirectDownloadCertificateHostStatusResponse createDirectDownloadCertificateProvisionResponse(Long certificateId, + Long hostId, Pair result); FirewallResponse createIpv6FirewallRuleResponse(FirewallRule acl); @@ -568,4 +599,14 @@ List createTemplateResponses(ResponseView view, VirtualMachine BackupRepositoryResponse createBackupRepositoryResponse(BackupRepository repository); SharedFSResponse createSharedFSResponse(ResponseView view, SharedFS sharedFS); + + void updateTemplateIsoResponsesForIcons(List responses, ResourceTag.ResourceObjectType type); + + GuiThemeResponse createGuiThemeResponse(GuiThemeJoin guiThemeJoin); + + ConsoleSessionResponse createConsoleSessionResponse(ConsoleSession consoleSession, ResponseView responseView); + + ApiKeyPairResponse createKeyPairResponse(ApiKeyPair keyPair); + + ListResponse createKeypairPermissionsResponse(List permissions); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java index ea25c56ee39e..cc154ed964b3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/CreateAccountCmd.java @@ -177,7 +177,7 @@ public long getEntityOwnerId() { @Override public void execute() { validateParams(); - CallContext.current().setEventDetails("Account Name: " + getUsername() + ", Domain Id:" + getDomainId()); + CallContext.current().setEventDetails("Account Name: " + getUsername() + ", Domain ID:" + getResourceUuid(ApiConstants.DOMAIN_ID)); UserAccount userAccount = _accountService.createUserAccount(this); if (userAccount != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java index 29774e254aa0..f7f8bd974272 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/account/DisableAccountCmd.java @@ -108,12 +108,20 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Disabling Account: " + getAccountName() + " in domain: " + getDomainId(); + String message = "Disabling Account "; + + if (getId() != null) { + message += "with ID: " + getResourceUuid(ApiConstants.ID); + } else { + message += getAccountName() + " in Domain: " + getResourceUuid(ApiConstants.DOMAIN_ID); + } + + return message; } @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException { - CallContext.current().setEventDetails("Account Name: " + getAccountName() + ", Domain Id:" + getDomainId()); + CallContext.current().setEventDetails("Account Name: " + getAccountName() + ", Domain Id:" + getResourceUuid(ApiConstants.DOMAIN_ID)); Account result = _regionService.disableAccount(this); if (result != null){ AccountResponse response = _responseGenerator.createAccountResponse(ResponseView.Full, result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java index 232c4760e1e6..13405431f63e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/CreateRolePermissionCmd.java @@ -81,7 +81,7 @@ public void execute() { if (role == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role id provided"); } - CallContext.current().setEventDetails("Role id: " + role.getId() + ", rule:" + getRule() + ", permission: " + getPermission() + ", description: " + getDescription()); + CallContext.current().setEventDetails("Role ID: " + role.getUuid() + ", rule:" + getRule() + ", permission: " + getPermission() + ", description: " + getDescription()); final RolePermission rolePermission = roleService.createRolePermission(role, getRule(), getPermission(), getDescription()); if (rolePermission == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create role permission"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRoleCmd.java index fd2d11aeda0a..80ec08260ab2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRoleCmd.java @@ -70,7 +70,7 @@ public void execute() { if (role == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find the role with provided id"); } - CallContext.current().setEventDetails("Role id: " + role.getId()); + CallContext.current().setEventDetails("Role ID: " + role.getUuid()); boolean result = roleService.deleteRole(role); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRolePermissionCmd.java index bedaca9e23af..cf4a62bf6c43 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DeleteRolePermissionCmd.java @@ -68,7 +68,7 @@ public void execute() { if (rolePermission == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role permission id provided"); } - CallContext.current().setEventDetails("Role permission id: " + rolePermission.getId()); + CallContext.current().setEventDetails("Role permission ID: " + rolePermission.getUuid()); boolean result = roleService.deleteRolePermission(rolePermission); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java index 80cb92c8362f..2c5659b2bc4b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/DisableRoleCmd.java @@ -55,7 +55,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE if (role == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find the role with provided id"); } - CallContext.current().setEventDetails("Role id: " + role.getId()); + CallContext.current().setEventDetails("Role ID: " + role.getUuid()); boolean result = roleService.disableRole(role); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java index c4a6505d52f6..05dfbe1270fa 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/EnableRoleCmd.java @@ -55,7 +55,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE if (role == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find the role with provided id"); } - CallContext.current().setEventDetails("Role id: " + role.getId()); + CallContext.current().setEventDetails("Role ID: " + role.getUuid()); boolean result = roleService.enableRole(role); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java index 8f8115e9957e..992564413f6b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/UpdateRolePermissionCmd.java @@ -111,7 +111,7 @@ public void execute() { if (getRuleId() != null || getRulePermission() != null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Parameters permission and ruleid must be mutually exclusive with ruleorder"); } - CallContext.current().setEventDetails("Reordering permissions for role id: " + role.getId()); + CallContext.current().setEventDetails("Reordering permissions for role with ID: " + role.getUuid()); final List rolePermissionsOrder = new ArrayList<>(); for (Long rolePermissionId : getRulePermissionOrder()) { final RolePermission rolePermission = roleService.findRolePermission(rolePermissionId); @@ -129,7 +129,7 @@ public void execute() { if (rolePermission == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid rule id provided"); } - CallContext.current().setEventDetails("Updating permission for rule id: " + getRuleId() + " to: " + getRulePermission().toString()); + CallContext.current().setEventDetails("Updating permission for rule with ID: " + getResourceUuid(ApiConstants.RULE_ID) + " to: " + getRulePermission().toString()); result = roleService.updateRolePermission(role, rolePermission, getRulePermission()); } SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java index d39c2312aa91..e085c10cee0b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/CreateProjectRolePermissionCmd.java @@ -72,7 +72,7 @@ public void execute() { if (projectRole == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid project role ID provided"); } - CallContext.current().setEventDetails("Project Role ID: " + projectRole.getId() + ", Rule:" + getRule() + ", Permission: " + getPermission() + ", Description: " + getDescription()); + CallContext.current().setEventDetails("Project Role ID: " + projectRole.getUuid() + ", Rule:" + getRule() + ", Permission: " + getPermission() + ", Description: " + getDescription()); final ProjectRolePermission projectRolePermission = projRoleService.createProjectRolePermission(this); if (projectRolePermission == null) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create project role permission"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java index 9f8d82489584..84f73e7a1a32 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRoleCmd.java @@ -69,7 +69,7 @@ public void execute() { if (role == null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Cannot find project role with provided id"); } - CallContext.current().setEventDetails("Deleting Project Role with id: " + role.getId()); + CallContext.current().setEventDetails("Deleting Project Role with ID: " + role.getUuid()); boolean result = projRoleService.deleteProjectRole(role, getProjectId()); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java index ac68278535e2..d7941a6a4cc3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/DeleteProjectRolePermissionCmd.java @@ -70,7 +70,7 @@ public void execute() { if (rolePermission == null || rolePermission.getProjectId() != getProjectId()) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid role permission id provided for the project"); } - CallContext.current().setEventDetails("Deleting Project Role permission with id: " + rolePermission.getId()); + CallContext.current().setEventDetails("Deleting Project Role permission with ID: " + rolePermission.getUuid()); boolean result = projRoleService.deleteProjectRolePermission(rolePermission); SuccessResponse response = new SuccessResponse(); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java index b273b9f58493..fd0c043f2321 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/acl/project/UpdateProjectRolePermissionCmd.java @@ -115,7 +115,7 @@ public void execute() { if (getProjectRuleId() != null || getProjectRolePermission() != null) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Parameters permission and ruleid must be mutually exclusive with ruleorder"); } - CallContext.current().setEventDetails("Reordering permissions for role id: " + projectRole.getId()); + CallContext.current().setEventDetails("Reordering permissions for role with ID: " + projectRole.getUuid()); result = updateProjectRolePermissionOrder(projectRole); } else if (getProjectRuleId() != null || getProjectRolePermission() != null ) { @@ -123,7 +123,7 @@ public void execute() { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Parameters permission and ruleid must be mutually exclusive with ruleorder"); } ProjectRolePermission rolePermission = getValidProjectRolePermission(); - CallContext.current().setEventDetails("Updating project role permission for rule id: " + getProjectRuleId() + " to: " + getProjectRolePermission().toString()); + CallContext.current().setEventDetails("Updating project role permission for rule ID: " + getProjectRuleId() + " to: " + getProjectRolePermission().toString()); result = projRoleService.updateProjectRolePermission(projectId, projectRole, rolePermission, getProjectRolePermission()); } SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/CreateCounterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/CreateCounterCmd.java index 4b0ca25691a8..d7be56bf3f46 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/CreateCounterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/CreateCounterCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.admin.autoscale; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -89,9 +90,6 @@ public void create() { if (ctr != null) { this.setEntityId(ctr.getId()); this.setEntityUuid(ctr.getUuid()); - CounterResponse response = _responseGenerator.createCounterResponse(ctr); - response.setResponseName(getCommandName()); - this.setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Counter with name " + getName()); } @@ -99,6 +97,11 @@ public void create() { @Override public void execute() { + CallContext.current().setEventDetails("Counter ID: " + getEntityUuid()); + Counter ctr = _autoScaleService.getCounter(getEntityId()); + CounterResponse response = _responseGenerator.createCounterResponse(ctr); + response.setResponseName(getCommandName()); + this.setResponseObject(response); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/DeleteCounterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/DeleteCounterCmd.java index fb0c9606c512..8e941965e84b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/DeleteCounterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/autoscale/DeleteCounterCmd.java @@ -61,7 +61,7 @@ public void execute() { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); } else { - logger.warn("Failed to delete counter with Id: " + getId()); + logger.warn("Failed to delete counter with Id: {}", getId()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete counter."); } } @@ -91,6 +91,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting a counter."; + return "Deleting auto scaling counter with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java new file mode 100644 index 000000000000..500a77f3d4fc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmd.java @@ -0,0 +1,166 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.exception.CloudRuntimeException; + +import java.util.Arrays; +import java.util.List; +import java.util.function.LongFunction; + +@APICommand(name = "cloneBackupOffering", + description = "Clones a backup offering from an existing offering", + responseObject = BackupOfferingResponse.class, since = "4.23.0", + authorized = {RoleType.Admin}) +public class CloneBackupOfferingCmd extends BaseAsyncCmd implements DomainAndZoneIdResolver { + + @Inject + protected BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + //////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.SOURCE_OFFERING_ID, type = BaseCmd.CommandType.UUID, entityType = BackupOfferingResponse.class, + required = true, description = "The ID of the source backup offering to clone from") + private Long sourceOfferingId; + + @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, required = true, + description = "The name of the cloned offering") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = BaseCmd.CommandType.STRING, required = false, + description = "The description of the cloned offering") + private String description; + + @Parameter(name = ApiConstants.EXTERNAL_ID, type = BaseCmd.CommandType.STRING, required = false, + description = "The backup offering ID (from backup provider side)") + private String externalId; + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + description = "The zone ID", required = false) + private Long zoneId; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.STRING, + description = "the ID of the containing domain(s) as comma separated string, public for public offerings", + length = 4096) + private String domainIds; + + @Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = BaseCmd.CommandType.BOOLEAN, + description = "Whether users are allowed to create adhoc backups and backup schedules", required = false) + private Boolean userDrivenBackups; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getSourceOfferingId() { + return sourceOfferingId; + } + + public String getName() { + return name; + } + + public String getExternalId() { + return externalId; + } + + public Long getZoneId() { + return zoneId; + } + + public String getDescription() { + return description; + } + + public Boolean getUserDrivenBackups() { + return userDrivenBackups; + } + + public List getDomainIds() { + if (domainIds != null && !domainIds.isEmpty()) { + return Arrays.asList(Arrays.stream(domainIds.split(",")).map(domainId -> Long.parseLong(domainId.trim())).toArray(Long[]::new)); + } + LongFunction> defaultDomainsProvider = null; + if (backupManager != null) { + defaultDomainsProvider = backupManager::getBackupOfferingDomains; + } + return resolveDomainIds(domainIds, sourceOfferingId, defaultDomainsProvider, "backup offering"); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + BackupOffering policy = backupManager.cloneBackupOffering(this); + if (policy == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone backup offering"); + } + BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(policy); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); + } catch (CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_OFFERING_CLONE; + } + + @Override + public String getEventDescription() { + return "Cloning backup offering: " + name + " from source offering: " + (sourceOfferingId == null ? "" : sourceOfferingId.toString()); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CreateImageTransferCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CreateImageTransferCmd.java new file mode 100644 index 000000000000..c98bfb850529 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/CreateImageTransferCmd.java @@ -0,0 +1,100 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.utils.EnumUtils; + +@APICommand(name = "createImageTransfer", + description = "Create image transfer for a disk in backup. This API is intended for testing only and is disabled by default.", + responseObject = ImageTransferResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class CreateImageTransferCmd extends BaseCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + description = "ID of the backup") + private Long backupId; + + @Parameter(name = ApiConstants.VOLUME_ID, + type = CommandType.UUID, + entityType = VolumeResponse.class, + required = true, + description = "ID of the disk/volume") + private Long volumeId; + + @Parameter(name = ApiConstants.DIRECTION, + type = CommandType.STRING, + required = true, + description = "Direction of the transfer: upload, download") + private String direction; + + @Parameter(name = ApiConstants.FORMAT, + type = CommandType.STRING, + description = "Format for the image transfer: raw/cow. 'raw' will create an NBD backend. 'cow' will use the File backend." + + "For download, only the 'raw' format is supported. Default: raw") + private String format; + + public Long getBackupId() { + return backupId; + } + + public Long getVolumeId() { + return volumeId; + } + + public ImageTransfer.Direction getDirection() { + return ImageTransfer.Direction.valueOf(direction); + } + + public ImageTransfer.Format getFormat() { + return EnumUtils.getEnum(ImageTransfer.Format.class, format); + } + + @Override + public void execute() { + ImageTransferResponse response = kvmBackupExportService.createImageTransfer(this); + response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteVmCheckpointCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteVmCheckpointCmd.java new file mode 100644 index 000000000000..d0e17e86d427 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/DeleteVmCheckpointCmd.java @@ -0,0 +1,85 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "deleteVirtualMachineCheckpoint", + description = "Delete a VM checkpoint. This API is intended for testing only and is disabled by default.", + responseObject = SuccessResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class DeleteVmCheckpointCmd extends BaseCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + @Parameter(name = "checkpointid", + type = CommandType.STRING, + required = true, + description = "Checkpoint ID") + private String checkpointId; + + public Long getVmId() { + return vmId; + } + + public String getCheckpointId() { + return checkpointId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setCheckpointId(String checkpointId) { + this.checkpointId = checkpointId; + } + + @Override + public void execute() { + boolean result = kvmBackupExportService.deleteVmCheckpoint(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java new file mode 100644 index 000000000000..45173f8668ee --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeBackupCmd.java @@ -0,0 +1,103 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; + +@APICommand(name = "finalizeBackup", + description = "Finalize a VM backup session. This API is intended for testing only and is disabled by default.", + responseObject = BackupResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class FinalizeBackupCmd extends BaseAsyncCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Inject + private BackupManager backupManager; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "ID of the backup") + private Long backupId; + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + @Override + public void execute() { + Backup backup = kvmBackupExportService.finalizeBackup(this); + + if (backup == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup"); + } + + BackupResponse response = backupManager.createBackupResponse(backup, null); + + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CREATE; + } + + @Override + public String getEventDescription() { + return "Finalizing backup " + backupId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeImageTransferCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeImageTransferCmd.java new file mode 100644 index 000000000000..dfc43e233bf2 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/FinalizeImageTransferCmd.java @@ -0,0 +1,69 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "finalizeImageTransfer", + description = "Finalize an image transfer. This API is intended for testing only and is disabled by default.", + responseObject = SuccessResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class FinalizeImageTransferCmd extends BaseCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ImageTransferResponse.class, + required = true, + description = "ID of the image transfer") + private Long imageTransferId; + + public Long getImageTransferId() { + return imageTransferId; + } + + @Override + public void execute() { + boolean result = kvmBackupExportService.finalizeImageTransfer(this); + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java index 2e73698e7aa1..4cf27c561508 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ImportBackupOfferingCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.BackupOffering; @@ -40,6 +41,11 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.commons.collections.CollectionUtils; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; @APICommand(name = "importBackupOffering", description = "Imports a backup offering using a backup provider", @@ -48,7 +54,7 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd { @Inject - private BackupManager backupManager; + protected BackupManager backupManager; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -76,6 +82,14 @@ public class ImportBackupOfferingCmd extends BaseAsyncCmd { description = "Whether users are allowed to create adhoc backups and backup schedules", required = true) private Boolean userDrivenBackups; + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = DomainResponse.class, + description = "the ID of the containing domain(s), null for public offerings", + since = "4.23.0") + private List domainIds; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -100,6 +114,15 @@ public Boolean getUserDrivenBackups() { return userDrivenBackups == null ? false : userDrivenBackups; } + public List getDomainIds() { + if (CollectionUtils.isNotEmpty(domainIds)) { + Set set = new LinkedHashSet<>(domainIds); + domainIds.clear(); + domainIds.addAll(set); + } + return domainIds; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -134,6 +157,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Importing backup offering: " + name + " (external ID: " + externalId + ") on zone ID " + zoneId ; + return "Importing backup offering: " + name + " (external ID: " + externalId + ") on zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID) ; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListImageTransfersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListImageTransfersCmd.java new file mode 100644 index 000000000000..d810d21ab5f8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListImageTransfersCmd.java @@ -0,0 +1,81 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "listImageTransfers", + description = "List image transfers for a backup. This API is intended for testing only and is disabled by default.", + responseObject = ImageTransferResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class ListImageTransfersCmd extends BaseListCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = ImageTransferResponse.class, + description = "ID of the Image Transfer") + private Long id; + + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + description = "ID of the backup") + private Long backupId; + + public Long getId() { + return id; + } + + public Long getBackupId() { + return backupId; + } + + @Override + public void execute() { + List responses = kvmBackupExportService.listImageTransfers(this); + ListResponse response = new ListResponse<>(); + response.setResponses(responses); + response.setObjectName(ImageTransfer.class.getSimpleName().toLowerCase()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListVmCheckpointsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListVmCheckpointsCmd.java new file mode 100644 index 000000000000..a61661e982de --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/ListVmCheckpointsCmd.java @@ -0,0 +1,69 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import java.util.List; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.CheckpointResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.KVMBackupExportService; + +@APICommand(name = "listVirtualMachineCheckpoints", + description = "List checkpoints for a VM. This API is intended for testing only and is disabled by default.", + responseObject = CheckpointResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) +public class ListVmCheckpointsCmd extends BaseListCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + public Long getVmId() { + return vmId; + } + + @Override + public void execute() { + List responses = kvmBackupExportService.listVmCheckpoints(this); + ListResponse response = new ListResponse<>(); + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return 0; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java new file mode 100644 index 000000000000..1bf6d45db049 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/StartBackupCmd.java @@ -0,0 +1,120 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import javax.inject.Inject; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.KVMBackupExportService; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; + +@APICommand(name = "startBackup", + description = "Start a VM backup session using pull mode backup-begin on the KVM host. This API is intended for testing only and is disabled by default.", + responseObject = BackupResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}) + public class StartBackupCmd extends BaseAsyncCreateCmd implements AdminCmd { + + @Inject + private KVMBackupExportService kvmBackupExportService; + + @Inject + private BackupManager backupManager; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, + type = CommandType.UUID, + entityType = UserVmResponse.class, + required = true, + description = "ID of the VM") + private Long vmId; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "the name of the backup") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the backup") + private String description; + + public Long getVmId() { + return vmId; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + @Override + public void execute() { + try { + Backup backup = kvmBackupExportService.startBackup(this); + BackupResponse response = backupManager.createBackupResponse(backup, null); + + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public void create() { + Backup backup = kvmBackupExportService.createBackup(this); + + if (backup != null) { + setEntityId(backup.getId()); + setEntityUuid(backup.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Backup"); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_BACKUP_CREATE; + } + + @Override + public String getEventDescription() { + return "Starting backup for Instance " + vmId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java index 9de06715ee74..2f0dd6acd0e1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/backup/UpdateBackupOfferingCmd.java @@ -25,19 +25,24 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.backup.BackupManager; import org.apache.cloudstack.backup.BackupOffering; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.exception.InvalidParameterValueException; import com.cloud.user.Account; import com.cloud.utils.exception.CloudRuntimeException; +import java.util.List; +import java.util.function.LongFunction; + @APICommand(name = "updateBackupOffering", description = "Updates a backup offering.", responseObject = BackupOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.16.0") -public class UpdateBackupOfferingCmd extends BaseCmd { +public class UpdateBackupOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { @Inject private BackupManager backupManager; @@ -57,6 +62,13 @@ public class UpdateBackupOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = CommandType.BOOLEAN, description = "Whether to allow user driven backups or not") private Boolean allowUserDrivenBackups; + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.STRING, + description = "the ID of the containing domain(s) as comma separated string, public for public offerings", + since = "4.23.0", + length = 4096) + private String domainIds; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -82,7 +94,7 @@ public Boolean getAllowUserDrivenBackups() { @Override public void execute() { try { - if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null) { + if (StringUtils.isAllEmpty(getName(), getDescription()) && getAllowUserDrivenBackups() == null && CollectionUtils.isEmpty(getDomainIds())) { throw new InvalidParameterValueException(String.format("Can't update Backup Offering [id: %s] because there are no parameters to be updated, at least one of the", "following should be informed: name, description or allowUserDrivenBackups.", id)); } @@ -98,11 +110,23 @@ public void execute() { this.setResponseObject(response); } catch (CloudRuntimeException e) { ApiErrorCode paramError = e instanceof InvalidParameterValueException ? ApiErrorCode.PARAM_ERROR : ApiErrorCode.INTERNAL_ERROR; - logger.error(String.format("Failed to update Backup Offering [id: %s] due to: [%s].", id, e.getMessage()), e); + logger.error("Failed to update Backup Offering [id: {}] due to: [{}].", id, e.getMessage(), e); throw new ServerApiException(paramError, e.getMessage()); } } + public List getDomainIds() { + // backupManager may be null in unit tests where the command is spied without injection. + // Avoid creating a method reference to a null receiver which causes NPE. When backupManager + // is null, pass null as the defaultDomainsProvider so resolveDomainIds will simply return + // an empty list or parse the explicit domainIds string. + LongFunction> defaultDomainsProvider = null; + if (backupManager != null) { + defaultDomainsProvider = backupManager::getBackupOfferingDomains; + } + return resolveDomainIds(domainIds, id, defaultDomainsProvider, "backup offering"); + } + @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java index 463af000f58b..79dad4269c9b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ca/IssueCertificateCmd.java @@ -149,6 +149,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "issuing certificate for domain(s)=" + domains + ", ip(s)=" + addresses; + return "Issuing certificate for domain(s)=" + domains + ", ip(s)=" + addresses; } } 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 af24e1f10c89..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); @@ -108,7 +118,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Provisioning certificate for host id=" + hostId + " using provider=" + provider; + return "Provisioning certificate for host with ID: " + getResourceUuid(ApiConstants.HOST_ID) + " using provider: " + provider; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java index 89cd4a05245e..d8fa2123d228 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/AddClusterCmd.java @@ -19,21 +19,22 @@ import java.util.ArrayList; import java.util.List; - -import com.cloud.cpu.CPU; -import org.apache.cloudstack.api.ApiCommandResourceType; +import java.util.Map; 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; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.ExtensionResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import com.cloud.cpu.CPU; import com.cloud.exception.DiscoveryException; import com.cloud.exception.ResourceInUseException; import com.cloud.org.Cluster; @@ -43,7 +44,6 @@ requestHasSensitiveInfo = true, responseHasSensitiveInfo = false) public class AddClusterCmd extends BaseCmd { - @Parameter(name = ApiConstants.CLUSTER_NAME, type = CommandType.STRING, required = true, description = "The cluster name") private String clusterName; @@ -65,11 +65,11 @@ public class AddClusterCmd extends BaseCmd { @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, required = true, - description = "Hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3") + description = "Hypervisor type of the cluster: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator,Ovm3,External") private String hypervisor; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "The CPU arch of the cluster. Valid options are: x86_64, aarch64", + description = "The CPU arch of the cluster. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; @@ -118,6 +118,26 @@ public class AddClusterCmd extends BaseCmd { private String ovm3cluster; @Parameter(name = ApiConstants.OVM3_VIP, type = CommandType.STRING, required = false, description = "Ovm3 vip to use for pool (and cluster)") private String ovm3vip; + + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, + type = CommandType.LIST, collectionType = CommandType.STRING, + description = "comma separated list of storage access groups for the hosts in the cluster", + since = "4.21.0") + private List storageAccessGroups; + + + @Parameter(name = ApiConstants.EXTENSION_ID, + type = CommandType.UUID, entityType = ExtensionResponse.class, + description = "UUID of the extension", + since = "4.21.0") + private Long extensionId; + + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, + type = CommandType.MAP, + description = "Details in key/value pairs to be added to the extension-resource mapping. Use the format externaldetails[i].=. Example: externaldetails[0].endpoint.url=https://example.com", + since = "4.21.0") + protected Map externalDetails; + public String getOvm3Pool() { return ovm3pool; } @@ -184,6 +204,10 @@ public String getHypervisor() { return hypervisor; } + public Long getExtensionId() { + return extensionId; + } + public String getClusterType() { return clusterType; } @@ -192,6 +216,10 @@ public void setClusterType(String type) { this.clusterType = type; } + public List getStorageAccessGroups() { + return storageAccessGroups; + } + @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; @@ -214,6 +242,10 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Map getExternalDetails() { + return convertDetailsToMap(externalDetails); + } + @Override public void execute() { try { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java index 60f2c2b1deea..00e7da6e37c1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ExecuteClusterDrsPlanCmd.java @@ -142,6 +142,6 @@ public String getEventType() { @Override public String getEventDescription() { - return String.format("Executing DRS plan for cluster: %d", getId()); + return "Executing DRS plan for cluster with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java index b464930b9dd5..9fe4e3f5cfc1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmd.java @@ -17,7 +17,11 @@ package org.apache.cloudstack.api.command.admin.cluster; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; + +import javax.inject.Inject; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -27,9 +31,13 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.extension.Extension; +import org.apache.cloudstack.extension.ExtensionHelper; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.cpu.CPU; +import com.cloud.hypervisor.Hypervisor; import com.cloud.org.Cluster; import com.cloud.utils.Pair; @@ -37,6 +45,8 @@ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListClustersCmd extends BaseListCmd { + @Inject + ExtensionHelper extensionHelper; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -74,6 +84,11 @@ public class ListClustersCmd extends BaseListCmd { since = "4.20.1") private String arch; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUP, type = CommandType.STRING, + description = "the name of the storage access group", + since = "4.21.0") + private String storageAccessGroup; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -122,10 +137,44 @@ public CPU.CPUArch getArch() { return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); } + public String getStorageAccessGroup() { + return storageAccessGroup; + } + + public ListClustersCmd() { + + } + + public ListClustersCmd(String storageAccessGroup) { + this.storageAccessGroup = storageAccessGroup; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// + protected void updateClustersExtensions(final List clusterResponses) { + if (CollectionUtils.isEmpty(clusterResponses)) { + return; + } + Map idExtensionMap = new HashMap<>(); + for (ClusterResponse response : clusterResponses) { + if (!Hypervisor.HypervisorType.External.getHypervisorDisplayName().equals(response.getHypervisorType())) { + continue; + } + Long extensionId = extensionHelper.getExtensionIdForCluster(response.getInternalId()); + if (extensionId == null) { + continue; + } + Extension extension = idExtensionMap.computeIfAbsent(extensionId, id -> extensionHelper.getExtension(id)); + if (extension == null) { + continue; + } + response.setExtensionId(extension.getUuid()); + response.setExtensionName(extension.getName()); + } + } + protected Pair, Integer> getClusterResponses() { Pair, Integer> result = _mgr.searchForClusters(this); List clusterResponses = new ArrayList<>(); @@ -134,6 +183,7 @@ protected Pair, Integer> getClusterResponses() { clusterResponse.setObjectName("cluster"); clusterResponses.add(clusterResponse); } + updateClustersExtensions(clusterResponses); return new Pair<>(clusterResponses, result.second()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java index fde463312ca7..77d0557af05b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/cluster/UpdateClusterCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.admin.cluster; +import java.util.Map; + import com.cloud.cpu.CPU; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -56,10 +58,16 @@ public class UpdateClusterCmd extends BaseCmd { private String managedState; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the cluster. Valid options are: x86_64, aarch64", + description = "the CPU arch of the cluster. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, + type = CommandType.MAP, + description = "Details in key/value pairs to be added to the extension-resource mapping. Use the format externaldetails[i].=. Example: externaldetails[0].endpoint.url=https://example.com", + since = "4.21.0") + protected Map externalDetails; + public String getClusterName() { return clusterName; } @@ -122,6 +130,10 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Map getExternalDetails() { + return convertDetailsToMap(externalDetails); + } + @Override public void execute() { Cluster cluster = _resourceService.getCluster(getId()); @@ -130,7 +142,7 @@ public void execute() { } Cluster result = _resourceService.updateCluster(this); if (result != null) { - ClusterResponse clusterResponse = _responseGenerator.createClusterResponse(cluster, false); + ClusterResponse clusterResponse = _responseGenerator.createClusterResponse(result, false); clusterResponse.setResponseName(getCommandName()); this.setResponseObject(clusterResponse); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java index f6f66415f533..055443934609 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ListCfgsByCmd.java @@ -19,23 +19,23 @@ import java.util.ArrayList; import java.util.List; -import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.commons.lang3.StringUtils; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.BaseListCmd; import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ImageStoreResponse; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ManagementServerResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; +import org.apache.commons.lang3.StringUtils; import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.Pair; @@ -94,6 +94,13 @@ public class ListCfgsByCmd extends BaseListCmd { description = "The ID of the Image Store to update the parameter value for corresponding image store") private Long imageStoreId; + @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, + type = CommandType.UUID, + entityType = ManagementServerResponse.class, + description = "the ID of the Management Server to update the parameter value for corresponding management server", + since = "4.23.0") + private Long managementServerId; + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "Lists configuration by group name (primarily used for UI)", since = "4.18.0") private String groupName; @@ -139,6 +146,10 @@ public Long getImageStoreId() { return imageStoreId; } + public Long getManagementServerId() { + return managementServerId; + } + public String getGroupName() { return groupName; } @@ -200,6 +211,9 @@ private void setScope(ConfigurationResponse cfgResponse) { if (getImageStoreId() != null){ cfgResponse.setScope("imagestore"); } + if (getManagementServerId() != null){ + cfgResponse.setScope("managementserver"); + } } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java index 2d511cff34db..3c3c36b29d7e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/ResetCfgCmd.java @@ -23,16 +23,16 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.ImageStoreResponse; -import org.apache.cloudstack.framework.config.ConfigKey; - import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ImageStoreResponse; +import org.apache.cloudstack.api.response.ManagementServerResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.framework.config.ConfigKey; import com.cloud.user.Account; import com.cloud.utils.Pair; @@ -84,6 +84,13 @@ public class ResetCfgCmd extends BaseCmd { description = "The ID of the Image Store to reset the parameter value for corresponding image store") private Long imageStoreId; + @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, + type = CommandType.UUID, + entityType = ManagementServerResponse.class, + description = "the ID of the Management Server to update the parameter value for corresponding management server", + since = "4.23.0") + private Long managementServerId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -116,6 +123,10 @@ public Long getImageStoreId() { return imageStoreId; } + public Long getManagementServerId() { + return managementServerId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -149,6 +160,9 @@ public void execute() { if (getImageStoreId() != null) { response.setScope(ConfigKey.Scope.ImageStore.name()); } + if (getManagementServerId() != null) { + response.setScope(ConfigKey.Scope.ManagementServer.name()); + } response.setValue(cfg.second()); this.setResponseObject(response); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java index 2fad8d71c68b..9db9529dc8d8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmd.java @@ -16,9 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.config; -import com.cloud.utils.crypt.DBEncryptionUtil; import org.apache.cloudstack.acl.RoleService; -import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; @@ -29,13 +27,17 @@ import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ImageStoreResponse; +import org.apache.cloudstack.api.response.ManagementServerResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.config.Configuration; +import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.commons.lang3.StringUtils; import com.cloud.user.Account; +import com.cloud.utils.crypt.DBEncryptionUtil; @APICommand(name = "updateConfiguration", description = "Updates a configuration.", responseObject = ConfigurationResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -88,6 +90,13 @@ public class UpdateCfgCmd extends BaseCmd { validations = ApiArgValidator.PositiveNumber) private Long imageStoreId; + @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, + type = CommandType.UUID, + entityType = ManagementServerResponse.class, + description = "the ID of the Management Server to update the parameter value for corresponding management server", + since = "4.23.0") + private Long managementServerId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -112,7 +121,7 @@ public Long getClusterId() { return clusterId; } - public Long getStoragepoolId() { + public Long getStoragePoolId() { return storagePoolId; } @@ -128,6 +137,10 @@ public Long getImageStoreId() { return imageStoreId; } + public Long getManagementServerId() { + return managementServerId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -150,7 +163,7 @@ public void execute() { ConfigurationResponse response = _responseGenerator.createConfigurationResponse(cfg); response.setResponseName(getCommandName()); response = setResponseScopes(response); - response = setResponseValue(response, cfg); + setResponseValue(response, cfg); this.setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update config"); @@ -161,15 +174,13 @@ public void execute() { * Sets the configuration value in the response. If the configuration is in the `Hidden` or `Secure` categories, the value is encrypted before being set in the response. * @param response to be set with the configuration `cfg` value * @param cfg to be used in setting the response value - * @return the response with the configuration's value */ - public ConfigurationResponse setResponseValue(ConfigurationResponse response, Configuration cfg) { + public void setResponseValue(ConfigurationResponse response, Configuration cfg) { + String value = cfg.getValue(); if (cfg.isEncrypted()) { - response.setValue(DBEncryptionUtil.encrypt(getValue())); - } else { - response.setValue(getValue()); + value = DBEncryptionUtil.encrypt(value); } - return response; + response.setValue(value); } /** @@ -184,7 +195,7 @@ public ConfigurationResponse setResponseScopes(ConfigurationResponse response) { if (getClusterId() != null) { response.setScope("cluster"); } - if (getStoragepoolId() != null) { + if (getStoragePoolId() != null) { response.setScope("storagepool"); } if (getAccountId() != null) { @@ -193,6 +204,9 @@ public ConfigurationResponse setResponseScopes(ConfigurationResponse response) { if (getDomainId() != null) { response.setScope("domain"); } + if (getManagementServerId() != null) { + response.setScope(ConfigKey.Scope.ManagementServer.name().toLowerCase()); + } return response; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java index 6a59788715ee..c140de5aa01e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/GetDiagnosticsDataCmd.java @@ -140,7 +140,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Getting diagnostics data files from System VM: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Getting diagnostics data files from System Instance with ID: " + getResourceUuid(ApiConstants.TARGET_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java index 577d86146fdd..d1f22baf6604 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/diagnostics/RunDiagnosticsCmd.java @@ -153,7 +153,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public String getEventDescription() { - return "Executing diagnostics on System VM: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Executing diagnostics on System Instance with ID: " + getResourceUuid(ApiConstants.TARGET_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java index 780198dded59..ad440376a913 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/direct/download/UploadTemplateDirectDownloadCertificateCmd.java @@ -95,7 +95,7 @@ public void execute() { } try { - logger.debug("Uploading certificate " + name + " to agents for Direct Download"); + logger.debug("Uploading certificate {} to agents for Direct Download", name); Pair> uploadStatus = directDownloadManager.uploadCertificateToHosts(certificate, name, hypervisor, zoneId, hostId); DirectDownloadCertificate certificate = uploadStatus.first(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/CreateDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/CreateDomainCmd.java index a20f69c90f58..d2775548a841 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/CreateDomainCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/CreateDomainCmd.java @@ -86,7 +86,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Domain Name: " + getDomainName() + ((getParentDomainId() != null) ? ", Parent DomainId :" + getParentDomainId() : "")); + CallContext.current().setEventDetails("Domain Name: " + getDomainName() + ((getParentDomainId() != null) ? ", Parent Domain ID:" + getResourceUuid(ApiConstants.PARENT_DOMAIN_ID) : "")); Domain domain = _domainService.createDomain(getDomainName(), getParentDomainId(), getNetworkDomain(), getDomainUUID()); if (domain != null) { DomainResponse response = _responseGenerator.createDomainResponse(domain); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java index 6adb457f4f83..cf02e6a56bf8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/DeleteDomainCmd.java @@ -88,12 +88,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting domain: " + getId(); + return "Deleting domain with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Domain Id: " + getId()); + CallContext.current().setEventDetails("Domain ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _regionService.deleteDomain(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java index 5c5a92c45cac..aa1978042265 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/ListDomainsCmd.java @@ -23,7 +23,7 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiConstants.DomainDetails; -import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.BaseListTaggedResourcesCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.user.UserCmd; @@ -39,7 +39,7 @@ @APICommand(name = "listDomains", description = "Lists domains and provides detailed information for listed domains", responseObject = DomainResponse.class, responseView = ResponseView.Restricted, entityType = {Domain.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class ListDomainsCmd extends BaseListCmd implements UserCmd { +public class ListDomainsCmd extends BaseListTaggedResourcesCmd implements UserCmd { private static final String s_name = "listdomainsresponse"; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java index adce521627fb..124a84931548 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/domain/UpdateDomainCmd.java @@ -82,7 +82,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Domain Id: " + getId()); + CallContext.current().setEventDetails("Domain ID: " + getResourceUuid(ApiConstants.ID)); Domain domain = _regionService.updateDomain(this); if (domain != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java new file mode 100644 index 000000000000..2faad89bf67e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmd.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.gpu.GpuCard; + + +@APICommand(name = "createGpuCard", description = "Creates a GPU card definition in the system", + responseObject = GpuCardResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0") +public class CreateGpuCardCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.DEVICE_ID, type = CommandType.STRING, required = true, + description = "the device ID of the GPU card") + private String deviceId; + + @Parameter(name = ApiConstants.DEVICE_NAME, type = CommandType.STRING, required = true, + description = "the device name of the GPU card") + private String deviceName; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "the display name of the GPU card") + private String name; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, required = true, + description = "the vendor name of the GPU card") + private String vendorName; + + @Parameter(name = ApiConstants.VENDOR_ID, type = CommandType.STRING, required = true, + description = "the vendor ID of the GPU card") + private String vendorId; + + // Optional parameters for the passthrough vGPU profile display properties + @Parameter(name = ApiConstants.VIDEORAM, type = CommandType.LONG, + description = "the video RAM size in MB for the passthrough vGPU profile") + private Long videoRam; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public String getName() { + return name; + } + + public String getVendorName() { + return vendorName; + } + + public String getVendorId() { + return vendorId; + } + + public Long getVideoRam() { + return videoRam; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + GpuCard gpuCard = gpuService.createGpuCard(this); + if (gpuCard != null) { + GpuCardResponse response = new GpuCardResponse(gpuCard); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create GPU card"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create GPU card: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuDeviceCmd.java new file mode 100644 index 000000000000..e6386082a448 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuDeviceCmd.java @@ -0,0 +1,123 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + + +@APICommand(name = "createGpuDevice", description = "Creates a GPU device manually on a host", + responseObject = GpuDeviceResponse.class, since = "4.21.0", requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class CreateGpuDeviceCmd extends BaseCmd { + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, required = true, + description = "ID of the host where the GPU device is located") + private Long hostId; + + @Parameter(name = ApiConstants.BUS_ADDRESS, type = CommandType.STRING, required = true, + description = "PCI bus address of the GPU device (e.g., 0000:01:00.0) or UUID for MDEV devices.") + private String busAddress; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + required = true, description = "ID of the GPU card type") + private Long gpuCardId; + + @Parameter(name = ApiConstants.VGPU_PROFILE_ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + required = true, description = "ID of the vGPU profile") + private Long vgpuProfileId; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, + description = "Type of GPU device (PCI, MDEV, VGPUOnly). Defaults to PCI.") + private String type; + + @Parameter(name = ApiConstants.PARENT_GPU_DEVICE_ID, type = CommandType.UUID, entityType = GpuDeviceResponse.class, + description = "ID of the parent GPU device (for virtual GPU devices)") + private Long parentGpuDeviceId; + + @Parameter(name = ApiConstants.NUMA_NODE, type = CommandType.STRING, + description = "NUMA node of the GPU device (e.g., 0, 1, etc.). This is optional and can be used to " + + "specify the NUMA node for the GPU device which is used during allocation. Defaults to -1") + private String numaNode; + + public Long getHostId() { + return hostId; + } + + public String getBusAddress() { + return busAddress; + } + + public Long getGpuCardId() { + return gpuCardId; + } + + public Long getVgpuProfileId() { + return vgpuProfileId; + } + + public GpuDevice.DeviceType getType() { + GpuDevice.DeviceType deviceType = GpuDevice.DeviceType.PCI; + if (StringUtils.isNotBlank(type)) { + deviceType = EnumUtils.getEnumIgnoreCase(GpuDevice.DeviceType.class, type); + if (deviceType == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid GPU device type: " + type); + } + } + return deviceType; + } + + public Long getParentGpuDeviceId() { + return parentGpuDeviceId; + } + + public String getNumaNode() { + if (StringUtils.isBlank(numaNode)) { + return "-1"; // Default value for NUMA node + } + return numaNode; + } + + @Override + public void execute() { + try { + GpuDeviceResponse response = gpuService.createGpuDevice(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.java new file mode 100644 index 000000000000..3210773b6f45 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmd.java @@ -0,0 +1,131 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; + + +@APICommand(name = "createVgpuProfile", description = "Creates a vGPU profile in the system", + responseObject = VgpuProfileResponse.class, requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, since = "4.21.0", authorized = {RoleType.Admin}) +public class CreateVgpuProfileCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "the name of the vGPU profile") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, + description = "the description of the vGPU profile") + private String description; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + required = true, description = "the GPU card ID associated with this GPU device") + private Long cardId; + + @Parameter(name = ApiConstants.MAX_VGPU_PER_PHYSICAL_GPU, type = CommandType.LONG, + description = "Max vGPU per physical GPU. This is used to calculate capacity.") + private Long maxVgpuPerPgpu; + + @Parameter(name = ApiConstants.VIDEORAM, type = CommandType.LONG, + description = "the video RAM size in MB") + private Long videoRam; + + @Parameter(name = ApiConstants.MAXHEADS, type = CommandType.LONG, + description = "the maximum number of display heads") + private Long maxHeads; + + @Parameter(name = ApiConstants.MAXRESOLUTIONX, type = CommandType.LONG, + description = "the maximum X resolution") + private Long maxResolutionX; + + @Parameter(name = ApiConstants.MAXRESOLUTIONY, type = CommandType.LONG, + description = "the maximum Y resolution") + private Long maxResolutionY; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Long getCardId() { + return cardId; + } + + public Long getMaxVgpuPerPgpu() { + return maxVgpuPerPgpu; + } + + public Long getVideoRam() { + return videoRam; + } + + public Long getMaxHeads() { + return maxHeads; + } + + public Long getMaxResolutionX() { + return maxResolutionX; + } + + public Long getMaxResolutionY() { + return maxResolutionY; + } + + @Override + public void execute() { + try { + VgpuProfileResponse response = gpuService.createVgpuProfile(this); + if (response != null) { + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create vGPU profile: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java new file mode 100644 index 000000000000..9a510ecdead9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmd.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + + +@APICommand(name = "deleteGpuCard", description = "Deletes a GPU card definition from the system", + responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0", authorized = {RoleType.Admin}) +public class DeleteGpuCardCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuCardResponse.class, required = true, + description = "the ID of the GPU card") + private Long id; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + @Override + public void execute() { + try { + boolean success = gpuService.deleteGpuCard(this); + if (success) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete GPU card"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete GPU card: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuDeviceCmd.java new file mode 100644 index 000000000000..9224afc66ecf --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuDeviceCmd.java @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +import java.util.List; + +@APICommand(name = "deleteGpuDevice", description = "Deletes a vGPU profile from the system", + responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0", authorized = {RoleType.Admin}) +public class DeleteGpuDeviceCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, + entityType = GpuDeviceResponse.class, required = true, + description = "comma separated list of IDs of the GPU device") + private List ids; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public List getIds() { + return ids; + } + + @Override + public void execute() { + try { + boolean success = gpuService.deleteGpuDevices(this); + if (success) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to delete vGPU profile: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java new file mode 100644 index 000000000000..a09a04199f57 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmd.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; + + +@APICommand(name = "deleteVgpuProfile", description = "Deletes a vGPU profile from the system", + responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0", authorized = {RoleType.Admin}) +public class DeleteVgpuProfileCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, required = true, + description = "the ID of the vGPU profile") + private Long id; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + @Override + public void execute() { + try { + boolean success = gpuService.deleteVgpuProfile(this); + if (success) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to delete vGPU profile: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java new file mode 100644 index 000000000000..83aca1a2eb64 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmd.java @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.context.CallContext; + + +@APICommand(name = "discoverGpuDevices", description = "Discovers available GPU devices on a host", + responseObject = GpuDeviceResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0", authorized = {RoleType.Admin}) +public class DiscoverGpuDevicesCmd extends BaseListCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HostResponse.class, required = true, + description = "ID of the host") + private Long id; + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation ////////////////// + /// ////////////////////////////////////////////////// + + @Override + public void execute() { + CallContext.current().setEventDetails("Discovering GPU Devices on host with ID: " + getResourceUuid(ApiConstants.ID)); + ListResponse response = gpuService.discoverGpuDevices(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmdByAdmin.java new file mode 100644 index 000000000000..b3c57713fcd1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmdByAdmin.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuDevicesCmd; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; + + +@APICommand(name = "listGpuDevices", description = "Lists all available GPU devices", + responseView = ResponseObject.ResponseView.Full, + responseObject = GpuDeviceResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0") +public class ListGpuDevicesCmdByAdmin extends ListGpuDevicesCmd implements AdminCmd { + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuDeviceResponse.class, + description = "ID of the GPU device") + private Long id; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, + description = "the host ID where the GPU device is attached") + private Long hostId; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + description = "the GPU card ID associated with the GPU device") + private Long gpuCardId; + + @Parameter(name = ApiConstants.VGPU_PROFILE_ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + description = "the vGPU profile ID assigned to the GPU device") + private Long vgpuProfileId; + + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getHostId() { + return hostId; + } + + public Long getGpuCardId() { + return gpuCardId; + } + + public Long getVgpuProfileId() { + return vgpuProfileId; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ManageGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ManageGpuDeviceCmd.java new file mode 100644 index 000000000000..5dfe6c3deee0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/ManageGpuDeviceCmd.java @@ -0,0 +1,78 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +import java.util.List; + +@APICommand(name = "manageGpuDevice", description = "Manages a GPU device", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class ManageGpuDeviceCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, + entityType = GpuDeviceResponse.class, required = true, + description = "comma separated list of IDs of the GPU device") + private List ids; + + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public List getIds() { + return ids; + } + + @Override + public void execute() { + try { + if (gpuService.enableGpuDevice(this)) { + SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to enable GPU device"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to enable GPU device: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UnmanageGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UnmanageGpuDeviceCmd.java new file mode 100644 index 000000000000..46de23ec44be --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UnmanageGpuDeviceCmd.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +import java.util.List; + +@APICommand(name = "unmanageGpuDevice", description = "Unmanage a GPU device", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin}) +public class UnmanageGpuDeviceCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, + entityType = GpuDeviceResponse.class, required = true, + description = "comma separated list of IDs of the GPU device") + private List ids; + + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public List getIds() { + return ids; + } + + @Override + public void execute() { + try { + if (gpuService.disableGpuDevice(this)) { + SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to disable GPU device"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to disable GPU device: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java new file mode 100644 index 000000000000..0061149a985f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmd.java @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.gpu.GpuCard; + + +@APICommand(name = "updateGpuCard", description = "Updates a GPU card definition in the system", + responseObject = GpuCardResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0", authorized = {RoleType.Admin}) +public class UpdateGpuCardCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuCardResponse.class, required = true, + description = "the ID of the GPU card") + private Long id; + + @Parameter(name = ApiConstants.DEVICE_NAME, type = CommandType.STRING, + description = "the device name of the GPU card") + private String deviceName; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the display name of the GPU card") + private String name; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, + description = "the vendor name of the GPU card") + private String vendorName; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getDeviceName() { + return deviceName; + } + + public String getName() { + return name; + } + + public String getVendorName() { + return vendorName; + } + + @Override + public void execute() { + try { + GpuCard gpuCard = gpuService.updateGpuCard(this); + if (gpuCard != null) { + GpuCardResponse response = new GpuCardResponse(gpuCard); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update GPU card"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update GPU card: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuDeviceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuDeviceCmd.java new file mode 100644 index 000000000000..5ad6e6e1a6f6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuDeviceCmd.java @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + + +@APICommand(name = "updateGpuDevice", description = "Updates an existing GPU device", + responseObject = GpuDeviceResponse.class, since = "4.21.0", requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class UpdateGpuDeviceCmd extends BaseCmd { + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuDeviceResponse.class, required = true, + description = "ID of the GPU device to update") + private Long id; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + description = "New GPU card ID") + private Long gpuCardId; + + @Parameter(name = ApiConstants.VGPU_PROFILE_ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + description = "New vGPU profile ID") + private Long vgpuProfileId; + + @Parameter(name = "type", type = CommandType.STRING, description = "New type of GPU device (PCI, MDEV, VGPUOnly)") + private String type; + + @Parameter(name = "parentgpudeviceid", type = CommandType.UUID, entityType = GpuDeviceResponse.class, + description = "New parent GPU device ID (for virtual GPU devices)") + private Long parentGpuDeviceId; + + @Parameter(name = ApiConstants.NUMA_NODE, type = CommandType.STRING, + description = "New NUMA node of the GPU device") + private String numaNode; + + public Long getId() { + return id; + } + + public Long getGpuCardId() { + return gpuCardId; + } + + public Long getVgpuProfileId() { + return vgpuProfileId; + } + + public GpuDevice.DeviceType getType() { + GpuDevice.DeviceType deviceType = null; + if (StringUtils.isNotBlank(type)) { + deviceType = EnumUtils.getEnumIgnoreCase(GpuDevice.DeviceType.class, type); + if (deviceType == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Invalid GPU device type: " + type); + } + } + return deviceType; + } + + public Long getParentGpuDeviceId() { + return parentGpuDeviceId; + } + + public String getNumaNode() { + return numaNode; + } + + @Override + public void execute() { + try { + GpuDeviceResponse response = gpuService.updateGpuDevice(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java new file mode 100644 index 000000000000..c8d60739bd45 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmd.java @@ -0,0 +1,129 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.VgpuProfileResponse; + + +@APICommand(name = "updateVgpuProfile", description = "Updates a vGPU profile in the system", + responseObject = VgpuProfileResponse.class, requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, since = "4.21.0", authorized = {RoleType.Admin}) +public class UpdateVgpuProfileCmd extends BaseCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, required = true, + description = "the ID of the vGPU profile") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the vGPU profile") + private String profileName; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, + description = "the description of the vGPU profile") + private String description; + + @Parameter(name = ApiConstants.MAX_VGPU_PER_PHYSICAL_GPU, type = CommandType.LONG, + description = "the maximum number of vGPUs per physical GPU") + private Long maxVgpuPerPgpu; + + @Parameter(name = ApiConstants.VIDEORAM, type = CommandType.LONG, + description = "the video RAM size in MB") + private Long videoRam; + + @Parameter(name = ApiConstants.MAXHEADS, type = CommandType.LONG, + description = "the maximum number of display heads") + private Long maxHeads; + + @Parameter(name = ApiConstants.MAXRESOLUTIONX, type = CommandType.LONG, + description = "the maximum X resolution") + private Long maxResolutionX; + + @Parameter(name = ApiConstants.MAXRESOLUTIONY, type = CommandType.LONG, + description = "the maximum Y resolution") + private Long maxResolutionY; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getProfileName() { + return profileName; + } + + public String getDescription() { + return description; + } + + public Long getMaxVgpuPerPgpu() { + return maxVgpuPerPgpu; + } + + public Long getVideoRam() { + return videoRam; + } + + public Long getMaxHeads() { + return maxHeads; + } + + public Long getMaxResolutionX() { + return maxResolutionX; + } + + public Long getMaxResolutionY() { + return maxResolutionY; + } + + @Override + public void execute() { + try { + VgpuProfileResponse response = gpuService.updateVgpuProfile(this); + if (response != null) { + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update vGPU profile"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to update vGPU profile: " + e.getMessage()); + } + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.java new file mode 100644 index 000000000000..f099de43f5d2 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmd.java @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.guest; + +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; + +import com.cloud.storage.GuestOsCategory; +import com.cloud.user.Account; + +@APICommand(name = "addOsCategory", + description = "Adds a new OS category", + responseObject = GuestOSCategoryResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.21.0", + authorized = {RoleType.Admin}) +public class AddGuestOsCategoryCmd extends BaseCmd { + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the OS category", + required = true) + private String name; + + @Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, + description = "Whether the category is featured or not") + private Boolean featured; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public boolean isFeatured() { + return Boolean.TRUE.equals(featured); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + GuestOsCategory guestOs = _mgr.addGuestOsCategory(this); + if (guestOs != null) { + GuestOSCategoryResponse response = _responseGenerator.createGuestOSCategoryResponse(guestOs); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add new OS category"); + } + + } + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.GuestOsCategory; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCmd.java index 1868d0412a18..c0e995c497d2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCmd.java @@ -120,7 +120,7 @@ public void create() { @Override public void execute() { - CallContext.current().setEventDetails("Guest OS Id: " + getEntityId()); + CallContext.current().setEventDetails("Guest OS ID: " + getEntityUuid()); GuestOS guestOs = _mgr.getAddedGuestOs(getEntityId()); if (guestOs != null) { GuestOSResponse response = _responseGenerator.createGuestOSResponse(guestOs); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java similarity index 53% rename from api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java index b3e7d2bec821..8cf62ee2aabb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/DeleteGuestOsCategoryCmd.java @@ -14,32 +14,39 @@ // KIND, either express or implied. See the License for the // specific language governing permissions and limitations // under the License. -package org.apache.cloudstack.api.command.admin.user; -import org.apache.cloudstack.api.ApiCommandResourceType; +package org.apache.cloudstack.api.command.admin.guest; +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; import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.RegisterResponse; -import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; +import org.apache.cloudstack.api.response.SuccessResponse; import com.cloud.user.Account; -import com.cloud.user.User; -@APICommand(name = "registerUserKeys", - responseObject = RegisterResponse.class, - description = "This command allows a user to register for the developer API, returning a secret key and an API key. This request is made through the integration API port, so it is a privileged command and must be made on behalf of a user. It is up to the implementer just how the username and password are entered, and then how that translates to an integration API request. Both secret key and API key should be returned to the user", - requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) -public class RegisterCmd extends BaseCmd { + +@APICommand(name = "deleteOsCategory", + description = "Deletes an OS category", + responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.21.0", + authorized = {RoleType.Admin}) +public class DeleteGuestOsCategoryCmd extends BaseCmd { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "User id") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + required = true, description = "ID of the OS category") private Long id; ///////////////////////////////////////////////////// @@ -50,44 +57,33 @@ public Long getId() { return id; } - public void setId(Long id) { - this.id = id; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @Override - public long getEntityOwnerId() { - User user = _entityMgr.findById(User.class, getId()); - if (user != null) { - return user.getAccountId(); + public void execute() { + boolean result = _mgr.deleteGuestOsCategory(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove OS category"); } - - return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked } @Override - public Long getApiResourceId() { - return id; + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; } @Override public ApiCommandResourceType getApiResourceType() { - return ApiCommandResourceType.User; + return ApiCommandResourceType.GuestOsCategory; } @Override - public void execute() { - String[] keys = _accountService.createApiKeyAndSecretKey(this); - RegisterResponse response = new RegisterResponse(); - if (keys != null) { - response.setApiKey(keys[0]); - response.setSecretKey(keys[1]); - } - response.setObjectName("userkeys"); - response.setResponseName(getCommandName()); - this.setResponseObject(response); + public Long getApiResourceId() { + return getId(); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsCmd.java index d38682ce5bb4..f5c7d965c13f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsCmd.java @@ -62,7 +62,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Guest OS Id: " + id); + CallContext.current().setEventDetails("Guest OS ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _mgr.removeGuestOs(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -74,7 +74,7 @@ public void execute() { @Override public String getEventDescription() { - return "Removing Guest OS: " + getId(); + return "Removing Guest OS with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsMappingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsMappingCmd.java index a472ab672c55..bd4a53889f25 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsMappingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/RemoveGuestOsMappingCmd.java @@ -62,7 +62,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Guest OS Mapping Id: " + id); + CallContext.current().setEventDetails("Guest OS Mapping ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _mgr.removeGuestOsMapping(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -74,7 +74,7 @@ public void execute() { @Override public String getEventDescription() { - return "Removing Guest OS Mapping: " + getId(); + return "Removing Guest OS Mapping with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java new file mode 100644 index 000000000000..4041967abe8a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmd.java @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.guest; + +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; + +import com.cloud.storage.GuestOsCategory; +import com.cloud.user.Account; + +@APICommand(name = "updateOsCategory", + description = "Updates an OS category", + responseObject = GuestOSCategoryResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.21.0", + authorized = {RoleType.Admin}) +public class UpdateGuestOsCategoryCmd extends BaseCmd { + + + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + required = true, description = "ID of the OS category") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name for the OS category") + private String name; + + @Parameter(name = ApiConstants.IS_FEATURED, type = CommandType.BOOLEAN, + description = "Whether the category is featured or not") + private Boolean featured; + + @Parameter(name = ApiConstants.SORT_KEY, type = CommandType.INTEGER, + description = "sort key of the OS category for listing") + private Integer sortKey; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Boolean isFeatured() { + return featured; + } + + public Integer getSortKey() { + return sortKey; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + GuestOsCategory guestOs = _mgr.updateGuestOsCategory(this); + if (guestOs != null) { + GuestOSCategoryResponse response = _responseGenerator.createGuestOSCategoryResponse(guestOs); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update OS category"); + } + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.GuestOsCategory; + } + + @Override + public Long getApiResourceId() { + return getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java index c98cd149ef30..035ff6a19e24 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmd.java @@ -16,8 +16,12 @@ // under the License. package org.apache.cloudstack.api.command.admin.guest; -import org.apache.commons.collections.MapUtils; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -25,18 +29,14 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.GuestOSResponse; -import org.apache.cloudstack.acl.RoleType; +import org.apache.commons.collections.MapUtils; import com.cloud.event.EventTypes; import com.cloud.storage.GuestOS; import com.cloud.user.Account; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; - @APICommand(name = "updateGuestOs", description = "Updates the information about Guest OS", responseObject = GuestOSResponse.class, since = "4.4.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class UpdateGuestOsCmd extends BaseAsyncCmd { @@ -50,7 +50,7 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSResponse.class, required = true, description = "UUID of the Guest OS") private Long id; - @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, required = true, description = "Unique display name for Guest OS") + @Parameter(name = ApiConstants.OS_DISPLAY_NAME, type = CommandType.STRING, description = "Unique display name for Guest OS") private String osDisplayName; @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs)") @@ -59,6 +59,12 @@ public class UpdateGuestOsCmd extends BaseAsyncCmd { @Parameter(name="forDisplay", type=CommandType.BOOLEAN, description="whether this guest OS is available for end users", authorized = {RoleType.Admin}) private Boolean display; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + description = "the ID of the OS category", since = "4.21.0") + private Long osCategoryId; + + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -71,9 +77,9 @@ public String getOsDisplayName() { return osDisplayName; } - public Map getDetails() { - Map detailsMap = new HashMap<>();; - if (MapUtils.isNotEmpty(detailsMap)) { + public Map getDetails() { + Map detailsMap = new HashMap<>(); + if (MapUtils.isNotEmpty(details)) { Collection servicesCollection = details.values(); Iterator iter = servicesCollection.iterator(); while (iter.hasNext()) { @@ -90,6 +96,10 @@ public Boolean getForDisplay() { return display; } + public Long getOsCategoryId() { + return osCategoryId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -113,7 +123,7 @@ public void execute() { @Override public String getEventDescription() { - return "Updating guest OS: " + getId(); + return "Updating guest OS with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsMappingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsMappingCmd.java index fc67ef0a7e76..161bb5323070 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsMappingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsMappingCmd.java @@ -86,7 +86,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Updating Guest OS Mapping: " + getId(); + return "Updating Guest OS with ID: " + getResourceUuid(ApiConstants.ID) + " mapping."; } @Override 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 d7707e197d64..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); } @@ -102,7 +103,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE if (!result) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to configure HA provider for the host"); } - CallContext.current().setEventDetails("Host Id:" + host.getId() + " HA configured with provider: " + getHaProvider()); + CallContext.current().setEventDetails("Host ID:" + host.getUuid() + " HA configured with provider: " + getHaProvider()); CallContext.current().putContextParameter(Host.class, host.getUuid()); setupResponse(result, host.getUuid()); @@ -115,6 +116,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Configure HA for host: " + getHostId(); + return "Configuring HA for host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java index 51554b7607dc..63c657a9e454 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForClusterCmd.java @@ -89,7 +89,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find cluster by ID: " + getClusterId()); } final boolean result = haConfigManager.disableHA(cluster); - CallContext.current().setEventDetails("Cluster Id:" + cluster.getId() + " HA enabled: false"); + CallContext.current().setEventDetails("Cluster ID:" + cluster.getUuid() + " HA enabled: false"); CallContext.current().putContextParameter(Cluster.class, cluster.getUuid()); setupResponse(result); @@ -102,7 +102,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Disable HA for cluster: " + getClusterId(); + return "Disabling HA for cluster with ID: " + getResourceUuid(ApiConstants.CLUSTER_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java index ad9c64145322..b90f731ff565 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForHostCmd.java @@ -91,7 +91,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE } final boolean result = haConfigManager.disableHA(host.getId(), HAResource.ResourceType.Host); - CallContext.current().setEventDetails("Host Id:" + host.getId() + " HA enabled: false"); + CallContext.current().setEventDetails("Host ID:" + host.getUuid() + " HA enabled: false"); CallContext.current().putContextParameter(Host.class, host.getUuid()); setupResponse(result, host.getUuid()); @@ -104,6 +104,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Disable HA for host: " + getHostId(); + return "Disabling HA for host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java index 1f0758459b5d..07a6fbd2b399 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/DisableHAForZoneCmd.java @@ -90,7 +90,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE } final boolean result = haConfigManager.disableHA(dataCenter); - CallContext.current().setEventDetails("Zone Id:" + dataCenter.getId() + " HA enabled: false"); + CallContext.current().setEventDetails("Zone ID:" + dataCenter.getUuid() + " HA enabled: false"); CallContext.current().putContextParameter(DataCenter.class, dataCenter.getUuid()); setupResponse(result); @@ -103,7 +103,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Disable HA for zone: " + getZoneId(); + return "Disabling HA for zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java index 3bb7a4c3070d..635fba988c60 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForClusterCmd.java @@ -90,7 +90,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE } final boolean result = haConfigManager.enableHA(cluster); - CallContext.current().setEventDetails("Cluster Id:" + cluster.getId() + " HA enabled: true"); + CallContext.current().setEventDetails("Cluster ID:" + cluster.getUuid() + " HA enabled: true"); CallContext.current().putContextParameter(Cluster.class, cluster.getUuid()); setupResponse(result); @@ -103,6 +103,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Enable HA for cluster: " + getClusterId(); + return "Enabling HA for cluster with ID: " + getResourceUuid(ApiConstants.CLUSTER_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java index f54767225432..0bda19a7ad3c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForHostCmd.java @@ -91,7 +91,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE } final boolean result = haConfigManager.enableHA(host.getId(), HAResource.ResourceType.Host); - CallContext.current().setEventDetails("Host Id:" + host.getId() + " HA enabled: true"); + CallContext.current().setEventDetails("Host ID:" + host.getUuid() + " HA enabled: true"); CallContext.current().putContextParameter(Host.class, host.getUuid()); setupResponse(result, host.getUuid()); @@ -104,6 +104,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Enable HA for host: " + getHostId(); + return "Enabling HA for host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java index 99607315c543..f6d0f62bb120 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/ha/EnableHAForZoneCmd.java @@ -90,7 +90,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE } final boolean result = haConfigManager.enableHA(dataCenter); - CallContext.current().setEventDetails("Zone Id:" + dataCenter.getId() + " HA enabled: true"); + CallContext.current().setEventDetails("Zone ID:" + dataCenter.getUuid() + " HA enabled: true"); CallContext.current().putContextParameter(DataCenter.class, dataCenter.getUuid()); setupResponse(result); @@ -103,7 +103,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Enable HA for zone: " + getZoneId(); + return "Enabling HA for zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java index d91828c89dbf..5a1758345609 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/AddHostCmd.java @@ -18,7 +18,7 @@ import java.util.ArrayList; import java.util.List; - +import java.util.Map; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -76,6 +76,18 @@ public class AddHostCmd extends BaseCmd { @Parameter(name = ApiConstants.HOST_TAGS, type = CommandType.LIST, collectionType = CommandType.STRING, description = "List of tags to be added to the host") private List hostTags; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, + type = CommandType.LIST, collectionType = CommandType.STRING, + description = "comma separated list of storage access groups for the host", + since = "4.21.0") + private List storageAccessGroups; + + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, + type = CommandType.MAP, + description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue", + since = "4.21.0") + protected Map externalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -116,10 +128,18 @@ public List getHostTags() { return hostTags; } + public List getStorageAccessGroups() { + return storageAccessGroups; + } + public String getAllocationState() { return allocationState; } + public Map getExternalDetails() { + return convertExternalDetailsToMap(externalDetails); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostAsDegradedCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostAsDegradedCmd.java index f68da1edcd17..56930d47b2ec 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostAsDegradedCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostAsDegradedCmd.java @@ -78,7 +78,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "declaring host: " + getId() + " as Degraded"; + return "Removing host with ID: " + getResourceUuid(ApiConstants.ID) + " from Degraded state."; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelMaintenanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostMaintenanceCmd.java similarity index 95% rename from api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelMaintenanceCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostMaintenanceCmd.java index 96882985f264..5d44bafb4b5c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelMaintenanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/CancelHostMaintenanceCmd.java @@ -33,7 +33,7 @@ @APICommand(name = "cancelHostMaintenance", description = "Cancels host maintenance.", responseObject = HostResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class CancelMaintenanceCmd extends BaseAsyncCmd { +public class CancelHostMaintenanceCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// @@ -76,7 +76,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Canceling maintenance for host: " + getId(); + return "Canceling maintenance for host with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeclareHostAsDegradedCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeclareHostAsDegradedCmd.java index 209d8b65fbab..1dd65a583706 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeclareHostAsDegradedCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/DeclareHostAsDegradedCmd.java @@ -78,7 +78,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "declaring host: " + getId() + " as Degraded"; + return "Declaring host with ID: " + getResourceUuid(ApiConstants.ID) + " as Degraded."; } @Override 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 972a9c8950c4..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 @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ManagementServerResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -106,9 +107,17 @@ public class ListHostsCmd extends BaseListCmd { @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, description = "Hypervisor type of host: XenServer,KVM,VMware,Hyperv,BareMetal,Simulator") private String hypervisor; + @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, description = "the id of the management server", since="4.21.0") + private Long managementServerId; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, description = "CPU Arch of the host", since = "4.20.1") private String arch; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUP, type = CommandType.STRING, + description = "the name of the storage access group", + since = "4.21.0") + private String storageAccessGroup; + @Parameter(name = ApiConstants.VERSION, type = CommandType.STRING, description = "the host version", since = "4.20.3") private String version; @@ -196,10 +205,26 @@ public String getHostOutOfBandManagementPowerState() { return outOfBandManagementPowerState; } + public Long getManagementServerId() { + return managementServerId; + } + public CPU.CPUArch getArch() { return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); } + public String getStorageAccessGroup() { + return storageAccessGroup; + } + + public ListHostsCmd() { + + } + + public ListHostsCmd(String storageAccessGroup) { + this.storageAccessGroup = storageAccessGroup; + } + public String getVersion() { return version; } @@ -227,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/host/PrepareForMaintenanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/PrepareForHostMaintenanceCmd.java similarity index 96% rename from api/src/main/java/org/apache/cloudstack/api/command/admin/host/PrepareForMaintenanceCmd.java rename to api/src/main/java/org/apache/cloudstack/api/command/admin/host/PrepareForHostMaintenanceCmd.java index 53fe6fe3dabc..843c7fd7fcbe 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/PrepareForMaintenanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/PrepareForHostMaintenanceCmd.java @@ -33,7 +33,7 @@ @APICommand(name = "prepareHostForMaintenance", description = "Prepares a host for maintenance.", responseObject = HostResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class PrepareForMaintenanceCmd extends BaseAsyncCmd { +public class PrepareForHostMaintenanceCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// @@ -76,7 +76,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "preparing host: " + getId() + " for maintenance"; + return "Preparing host with ID: " + getResourceUuid(ApiConstants.ID) + " for maintenance."; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReconnectHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReconnectHostCmd.java index 178a96cedbd6..b9892ed6033c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReconnectHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReconnectHostCmd.java @@ -77,7 +77,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "reconnecting host: " + getId(); + return "Reconnecting host with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReleaseHostReservationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReleaseHostReservationCmd.java index d7905421a8f3..bddb5b13e452 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReleaseHostReservationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/ReleaseHostReservationCmd.java @@ -72,7 +72,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "releasing reservation for host: " + getId(); + return "Releasing reservation from host with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java index fa7376f74bbf..69bca7c79e21 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/host/UpdateHostCmd.java @@ -16,8 +16,9 @@ // under the License. package org.apache.cloudstack.api.command.admin.host; -import com.cloud.host.Host; -import com.cloud.user.Account; +import java.util.List; +import java.util.Map; + import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -28,7 +29,8 @@ import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.HostResponse; -import java.util.List; +import com.cloud.host.Host; +import com.cloud.user.Account; @APICommand(name = "updateHost", description = "Updates a host.", responseObject = HostResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -47,9 +49,14 @@ public class UpdateHostCmd extends BaseCmd { @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, - description = "The ID of OS category to update the host with") + description = "the ID of OS category used to prioritize VMs with matching OS category during the allocation process. " + + "It cannot be used alongside the 'guestosrule' parameter.") private Long osCategoryId; + @Parameter(name = ApiConstants.GUEST_OS_RULE, type = CommandType.STRING, description = "the guest OS rule written in JavaScript to match with the OS of the VM." + + "It cannot be used alongside the 'oscategoryid' parameter.") + private String guestOsRule; + @Parameter(name = ApiConstants.ALLOCATION_STATE, type = CommandType.STRING, description = "Change resource state of host, valid values are [Enable, Disable]. Operation may failed if host in states not allowing Enable/Disable") @@ -67,6 +74,17 @@ public class UpdateHostCmd extends BaseCmd { @Parameter(name = ApiConstants.ANNOTATION, type = CommandType.STRING, description = "Add an annotation to this host", since = "4.11", authorized = {RoleType.Admin}) private String annotation; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue", since = "4.21.0") + protected Map externalDetails; + + @Parameter(name = ApiConstants.CLEAN_UP_EXTERNAL_DETAILS, + type = CommandType.BOOLEAN, + description = "Optional boolean field, which indicates if external details should be cleaned up or not " + + "(If set to true, external details removed for this host, externaldetails field ignored; " + + "if false or not set, no action)", + since = "4.22.0") + protected Boolean cleanupExternalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -83,6 +101,10 @@ public Long getOsCategoryId() { return osCategoryId; } + public String getGuestOsRule() { + return guestOsRule; + } + public String getAllocationState() { return allocationState; } @@ -103,6 +125,14 @@ public String getAnnotation() { return annotation; } + public Map getExternalDetails() { + return convertExternalDetailsToMap(externalDetails); + } + + public boolean isCleanupExternalDetails() { + return Boolean.TRUE.equals(cleanupExternalDetails); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -126,7 +156,7 @@ public void execute() { this.setResponseObject(hostResponse); } catch (Exception e) { Host host = _entityMgr.findById(Host.class, getId()); - logger.debug("Failed to update host: {} with id {}", host, getId(), e); + logger.error("Failed to update {}", host, e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Failed to update host: %s with id %d, %s", host, getId(), e.getMessage())); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ConfigureInternalLoadBalancerElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ConfigureInternalLoadBalancerElementCmd.java index 9bb28523ecad..51aa86546603 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ConfigureInternalLoadBalancerElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/ConfigureInternalLoadBalancerElementCmd.java @@ -84,12 +84,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "configuring internal load balancer element: " + id; + return "Configuring internal load balancer element with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Internal load balancer element: " + id); + CallContext.current().setEventDetails("Internal load balancer element: " + getResourceUuid(ApiConstants.ID)); InternalLoadBalancerElementService service = _networkService.getInternalLoadBalancerElementById(id); VirtualRouterProvider result = service.configureInternalLoadBalancerElement(getId(), getEnabled()); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/CreateInternalLoadBalancerElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/CreateInternalLoadBalancerElementCmd.java index 474bbc831e5c..aa9e5f1ba7f4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/CreateInternalLoadBalancerElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/CreateInternalLoadBalancerElementCmd.java @@ -74,7 +74,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Virtual router element Id: " + getEntityId()); + CallContext.current().setEventDetails("Virtual router element ID: " + getEntityUuid()); InternalLoadBalancerElementService service = _networkService.getInternalLoadBalancerElementByNetworkServiceProviderId(getNspId()); VirtualRouterProvider result = service.getInternalLoadBalancerElement(getEntityId()); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StartInternalLBVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StartInternalLBVMCmd.java index b5aa3c8d9b07..d9d4e46726fc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StartInternalLBVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StartInternalLBVMCmd.java @@ -88,7 +88,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "starting Internal LB Instance: " + getId(); + return "Starting internal LB Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -103,7 +103,7 @@ public Long getApiResourceId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Internal LB Instance ID: " + getId()); + CallContext.current().setEventDetails("Internal LB Instance ID: " + getResourceUuid(ApiConstants.ID)); VirtualRouter result = null; VirtualRouter router = _routerService.findRouter(getId()); if (router == null || router.getRole() != Role.INTERNAL_LB_VM) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StopInternalLBVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StopInternalLBVMCmd.java index 82eddb27c7dd..253c59e671e5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StopInternalLBVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/internallb/StopInternalLBVMCmd.java @@ -86,7 +86,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Stopping Internal LB Instance: " + getId(); + return "Stopping Internal LB Instance: " + getResourceUuid(ApiConstants.ID); } @Override @@ -105,7 +105,7 @@ public boolean isForced() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException { - CallContext.current().setEventDetails("Internal LB Instance Id: " + getId()); + CallContext.current().setEventDetails("Internal LB Instance ID: " + getResourceUuid(ApiConstants.ID)); VirtualRouter result = null; VirtualRouter vm = _routerService.findRouter(getId()); if (vm == null || vm.getRole() != Role.INTERNAL_LB_VM) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsoPermissionsCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsoPermissionsCmdByAdmin.java index c4b9db3b7c38..b831a99cb0af 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsoPermissionsCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/iso/ListIsoPermissionsCmdByAdmin.java @@ -1,4 +1,4 @@ -// Licensedname = "listIsoPermissions", to the Apache Software Foundation (ASF) under one +// 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 diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java new file mode 100644 index 000000000000..358f7f749f6e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/kms/MigrateVolumesToKMSCmd.java @@ -0,0 +1,131 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.kms; + +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.KMSKey; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; +import java.util.List; + +@APICommand(name = "migrateVolumesToKMS", + description = "Migrates encrypted volumes to KMS", + responseObject = AsyncJobResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class MigrateVolumesToKMSCmd extends BaseAsyncCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ACCOUNT, + type = CommandType.STRING, + description = "Migrate volumes for specific account") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "Domain ID") + private Long domainId; + + @Parameter(name = ApiConstants.VOLUME_IDS, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = VolumeResponse.class, + required = true, + description = "List of volume IDs to migrate") + private List volumeIds; + + @Parameter(name = ApiConstants.KMS_KEY_ID, + required = true, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "KMS Key ID to use for migrating volumes") + private Long kmsKeyId; + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + public List getVolumeIds() { + return volumeIds; + } + + public Long getKmsKeyId() { + return kmsKeyId; + } + + @Override + public void execute() { + try { + kmsManager.migrateVolumesToKMS(this); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to migrate volumes to KMS: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + KMSKey key = _entityMgr.findById(KMSKey.class, kmsKeyId); + if (key != null) { + return key.getAccountId(); + } + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return com.cloud.event.EventTypes.EVENT_VOLUME_MIGRATE_TO_KMS; + } + + @Override + public String getEventDescription() { + return "Migrating volumes to KMS key: " + _uuidMgr.getUuid(KMSKey.class, kmsKeyId); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KmsKey; + } + + @Override + public Long getApiResourceId() { + return kmsKeyId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/management/RemoveManagementServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/management/RemoveManagementServerCmd.java new file mode 100644 index 000000000000..803b95bfa9e2 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/management/RemoveManagementServerCmd.java @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.management; + +import com.cloud.event.EventTypes; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.response.ManagementServerResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "removeManagementServer", description = "Removes a Management Server.", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = RoleType.Admin) +public class RemoveManagementServerCmd extends BaseCmd { + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, required = true, description = "the ID of the Management Server") + private Long id; + + public Long getId() { + return id; + } + + @Override + public void execute() { + boolean result = _mgr.removeManagementServer(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to remove Management Server."); + } + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } + + public String getEventType() { + return EventTypes.EVENT_MANAGEMENT_SERVER_REMOVE; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkServiceProviderCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkServiceProviderCmd.java index a0013f9d6e2b..3e42a0103d8b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkServiceProviderCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/AddNetworkServiceProviderCmd.java @@ -101,7 +101,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Network ServiceProvider Id: " + getEntityId()); + CallContext.current().setEventDetails("Network ServiceProvider ID: " + getEntityUuid()); PhysicalNetworkServiceProvider result = _networkService.getCreatedPhysicalNetworkServiceProvider(getEntityId()); if (result != null) { ProviderResponse response = _responseGenerator.createNetworkServiceProviderResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmd.java new file mode 100644 index 000000000000..19760ffaaa10 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmd.java @@ -0,0 +1,113 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.network; + +import java.util.List; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.NetworkOfferingResponse; + +import com.cloud.offering.NetworkOffering; + +@APICommand(name = "cloneNetworkOffering", + description = "Clones a network offering. All parameters are copied from the source offering unless explicitly overridden. " + + "Use 'addServices' and 'dropServices' to modify the service list without respecifying everything.", + responseObject = NetworkOfferingResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.23.0") +public class CloneNetworkOfferingCmd extends NetworkOfferingBaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.SOURCE_OFFERING_ID, + type = BaseCmd.CommandType.UUID, + entityType = NetworkOfferingResponse.class, + required = true, + description = "The ID of the source network offering to clone from") + private Long sourceOfferingId; + + @Parameter(name = "addservices", + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = "Services to add to the cloned offering (in addition to source offering services). " + + "If specified along with 'supportedservices', this parameter is ignored.") + private List addServices; + + @Parameter(name = "dropservices", + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = "Services to remove from the cloned offering (that exist in source offering). " + + "If specified along with 'supportedservices', this parameter is ignored.") + private List dropServices; + + @Parameter(name = ApiConstants.TRAFFIC_TYPE, + type = CommandType.STRING, + description = "The traffic type for the network offering. Supported type in current release is GUEST only") + private String traffictype; + + @Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, description = "Guest type of the network offering: Shared or Isolated") + private String guestIptype; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getSourceOfferingId() { + return sourceOfferingId; + } + + public List getAddServices() { + return addServices; + } + + public List getDropServices() { + return dropServices; + } + + public String getGuestIpType() { + return guestIptype; + } + + public String getTraffictype() { + return traffictype; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + NetworkOffering result = _configService.cloneNetworkOffering(this); + if (result != null) { + NetworkOfferingResponse response = _responseGenerator.createNetworkOfferingResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone network offering"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java index f6b035c57837..614dcf9d0751 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateGuestNetworkIpv6PrefixCmd.java @@ -83,7 +83,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating guest IPv6 prefix " + getPrefix() + " for zone=" + getZoneId(); + return "Creating guest IPv6 prefix " + getPrefix() + " for zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmd.java index a482cb1d4f27..4d645376a909 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmd.java @@ -85,7 +85,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating guest IPv4 subnet " + getSubnet() + " in zone subnet=" + getParentId(); + return "Creating guest IPv4 subnet " + getSubnet() + " in zone subnet: " + getResourceUuid(ApiConstants.PARENT_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmd.java index 5f48cf9c6327..48a6002fb5c0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmd.java @@ -102,7 +102,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating guest IPv4 subnet " + getSubnet() + " for zone=" + getZoneId(); + return "Creating guest IPv4 subnet " + getSubnet() + " for zone: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java index a7826e022a68..2780c4eaf050 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateManagementNetworkIpRangeCmd.java @@ -132,7 +132,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating management ip range from " + getStartIp() + " to " + getEndIp() + " and gateway=" + getGateWay() + ", netmask=" + getNetmask() + " of pod=" + getPodId(); + return "Creating management IP range from " + getStartIp() + " to " + getEndIp() + ", with gateway: " + getGateWay() + ", netmask:" + getNetmask() + " on pod:" + getPodId(); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java index c6a9a494f7cf..5c39060f9fa3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreateNetworkOfferingCmd.java @@ -16,477 +16,47 @@ // under the License. package org.apache.cloudstack.api.command.admin.network; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; - -import com.cloud.network.Network; -import com.cloud.network.VirtualRouterProvider; -import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.lang3.BooleanUtils; -import org.apache.commons.lang3.StringUtils; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.NetworkOfferingResponse; -import org.apache.cloudstack.api.response.ServiceOfferingResponse; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.network.Network.Capability; -import com.cloud.network.Network.Service; import com.cloud.offering.NetworkOffering; -import com.cloud.offering.NetworkOffering.Availability; -import com.cloud.user.Account; - -import static com.cloud.network.Network.Service.Dhcp; -import static com.cloud.network.Network.Service.Dns; -import static com.cloud.network.Network.Service.Lb; -import static com.cloud.network.Network.Service.StaticNat; -import static com.cloud.network.Network.Service.SourceNat; -import static com.cloud.network.Network.Service.PortForwarding; -import static com.cloud.network.Network.Service.NetworkACL; -import static com.cloud.network.Network.Service.UserData; -import static com.cloud.network.Network.Service.Firewall; @APICommand(name = "createNetworkOffering", description = "Creates a network offering.", responseObject = NetworkOfferingResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class CreateNetworkOfferingCmd extends BaseCmd { +public class CreateNetworkOfferingCmd extends NetworkOfferingBaseCmd { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the network offering") - private String networkOfferingName; - - @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the network offering, defaults to the value of 'name'.") - private String displayText; - @Parameter(name = ApiConstants.TRAFFIC_TYPE, type = CommandType.STRING, required = true, description = "The traffic type for the network offering. Supported type in current release is GUEST only") private String traffictype; - @Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "The tags for the network offering.", length = 4096) - private String tags; - - @Parameter(name = ApiConstants.SPECIFY_VLAN, type = CommandType.BOOLEAN, description = "True if network offering supports VLANs") - private Boolean specifyVlan; - - @Parameter(name = ApiConstants.AVAILABILITY, type = CommandType.STRING, description = "The availability of network offering. The default value is Optional. " - + " Another value is Required, which will make it as the default network offering for new networks ") - private String availability; - - @Parameter(name = ApiConstants.NETWORKRATE, type = CommandType.INTEGER, description = "Data transfer rate in megabits per second allowed") - private Integer networkRate; - - @Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN, description = "True if the network offering is IP conserve mode enabled") - private Boolean conserveMode; - - @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, - type = CommandType.UUID, - entityType = ServiceOfferingResponse.class, - description = "The service offering ID used by virtual router provider") - private Long serviceOfferingId; - @Parameter(name = ApiConstants.GUEST_IP_TYPE, type = CommandType.STRING, required = true, description = "Guest type of the network offering: Shared or Isolated") private String guestIptype; - @Parameter(name = ApiConstants.INTERNET_PROTOCOL, - type = CommandType.STRING, - description = "The internet protocol of network offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create a network offering that supports both IPv4 and IPv6", - since = "4.17.0") - private String internetProtocol; - - @Parameter(name = ApiConstants.SUPPORTED_SERVICES, - type = CommandType.LIST, - collectionType = CommandType.STRING, - description = "Services supported by the network offering") - private List supportedServices; - - @Parameter(name = ApiConstants.SERVICE_PROVIDER_LIST, - type = CommandType.MAP, - description = "Provider to service mapping. If not specified, the provider for the service will be mapped to the default provider on the physical network") - private Map serviceProviderList; - - @Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of network offering") - private Map serviceCapabilitystList; - - @Parameter(name = ApiConstants.SPECIFY_IP_RANGES, - type = CommandType.BOOLEAN, - description = "True if network offering supports specifying ip ranges; defaulted to false if not specified") - private Boolean specifyIpRanges; - - @Parameter(name = ApiConstants.IS_PERSISTENT, - type = CommandType.BOOLEAN, - description = "True if network offering supports persistent networks; defaulted to false if not specified") - private Boolean isPersistent; - - @Parameter(name = ApiConstants.FOR_VPC, - type = CommandType.BOOLEAN, - description = "True if network offering is meant to be used for VPC, false otherwise.") - private Boolean forVpc; - - @Parameter(name = ApiConstants.FOR_NSX, - type = CommandType.BOOLEAN, - description = "true if network offering is meant to be used for NSX, false otherwise.", - since = "4.20.0") - private Boolean forNsx; - - @Parameter(name = ApiConstants.NSX_SUPPORT_LB, - type = CommandType.BOOLEAN, - description = "True if network offering for NSX network offering supports Load balancer service.", - since = "4.20.0") - private Boolean nsxSupportsLbService; - - @Parameter(name = ApiConstants.NSX_SUPPORTS_INTERNAL_LB, - type = CommandType.BOOLEAN, - description = "True if network offering for NSX network offering supports Internal Load balancer service.", - since = "4.20.0") - private Boolean nsxSupportsInternalLbService; - - @Parameter(name = ApiConstants.NETWORK_MODE, - type = CommandType.STRING, - description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", - since = "4.20.0") - private String networkMode; - - @Parameter(name = ApiConstants.FOR_TUNGSTEN, - type = CommandType.BOOLEAN, - description = "True if network offering is meant to be used for Tungsten-Fabric, false otherwise.") - private Boolean forTungsten; - - @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.2.0", description = "Network offering details in key/value pairs." - + " Supported keys are internallbprovider/publiclbprovider with service provider as a value, and" - + " promiscuousmode/macaddresschanges/forgedtransmits with true/false as value to accept/reject the security settings if available for a nic/portgroup") - protected Map details; - - @Parameter(name = ApiConstants.EGRESS_DEFAULT_POLICY, - type = CommandType.BOOLEAN, - description = "True if guest network default egress policy is allow; false if default egress policy is deny") - private Boolean egressDefaultPolicy; - - @Parameter(name = ApiConstants.KEEPALIVE_ENABLED, - type = CommandType.BOOLEAN, - required = false, - description = "If true keepalive will be turned on in the loadbalancer. At the time of writing this has only an effect on haproxy; the mode http and httpclose options are unset in the haproxy conf file.") - private Boolean keepAliveEnabled; - - @Parameter(name = ApiConstants.MAX_CONNECTIONS, - type = CommandType.INTEGER, - description = "Maximum number of concurrent connections supported by the Network offering") - private Integer maxConnections; - - @Parameter(name = ApiConstants.DOMAIN_ID, - type = CommandType.LIST, - collectionType = CommandType.UUID, - entityType = DomainResponse.class, - description = "The ID of the containing domain(s), null for public offerings") - private List domainIds; - - @Parameter(name = ApiConstants.ZONE_ID, - type = CommandType.LIST, - collectionType = CommandType.UUID, - entityType = ZoneResponse.class, - description = "The ID of the containing zone(s), null for public offerings", - since = "4.13") - private List zoneIds; - - @Parameter(name = ApiConstants.ENABLE, - type = CommandType.BOOLEAN, - description = "Set to true if the offering is to be enabled during creation. Default is false", - since = "4.16") - private Boolean enable; - - @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", - description = "true if network offering supports choosing AS number") - private Boolean specifyAsNumber; - - @Parameter(name = ApiConstants.ROUTING_MODE, - type = CommandType.STRING, - since = "4.20.0", - description = "the routing mode for the network offering. Supported types are: Static or Dynamic.") - private String routingMode; - ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public String getNetworkOfferingName() { - return networkOfferingName; - } - - public String getDisplayText() { - return StringUtils.isEmpty(displayText) ? networkOfferingName : displayText; - } - - public String getTags() { - return tags; - } - public String getTraffictype() { return traffictype; } - public Boolean getSpecifyVlan() { - return specifyVlan == null ? false : specifyVlan; - } - - public String getAvailability() { - return availability == null ? Availability.Optional.toString() : availability; - } - - public Integer getNetworkRate() { - return networkRate; - } - - public Long getServiceOfferingId() { - return serviceOfferingId; - } - - public List getSupportedServices() { - if (!isForNsx()) { - return supportedServices == null ? new ArrayList() : supportedServices; - } else { - List services = new ArrayList<>(List.of( - Dhcp.getName(), - Dns.getName(), - StaticNat.getName(), - SourceNat.getName(), - PortForwarding.getName(), - UserData.getName() - )); - if (getNsxSupportsLbService()) { - services.add(Lb.getName()); - } - if (Boolean.TRUE.equals(forVpc)) { - services.add(NetworkACL.getName()); - } else { - services.add(Firewall.getName()); - } - return services; - } - } - public String getGuestIpType() { return guestIptype; } - public String getInternetProtocol() { - return internetProtocol; - } - - public Boolean getSpecifyIpRanges() { - return specifyIpRanges == null ? false : specifyIpRanges; - } - - public Boolean getConserveMode() { - if (conserveMode == null) { - return true; - } - return conserveMode; - } - - public Boolean getIsPersistent() { - return isPersistent == null ? false : isPersistent; - } - - public Boolean getForVpc() { - return forVpc; - } - - public boolean isForNsx() { - return BooleanUtils.isTrue(forNsx); - } - - public String getNetworkMode() { - return networkMode; - } - - public boolean getNsxSupportsLbService() { - return BooleanUtils.isTrue(nsxSupportsLbService); - } - - public boolean getNsxSupportsInternalLbService() { - return BooleanUtils.isTrue(nsxSupportsInternalLbService); - } - - public Boolean getForTungsten() { - return forTungsten; - } - - public Boolean getEgressDefaultPolicy() { - if (egressDefaultPolicy == null) { - return true; - } - return egressDefaultPolicy; - } - - public Boolean getKeepAliveEnabled() { - return keepAliveEnabled; - } - - public Integer getMaxconnections() { - return maxConnections; - } - - public Map> getServiceProviders() { - Map> serviceProviderMap = new HashMap<>(); - if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isForNsx()) { - Collection servicesCollection = serviceProviderList.values(); - Iterator iter = servicesCollection.iterator(); - while (iter.hasNext()) { - HashMap services = (HashMap) iter.next(); - String service = services.get("service"); - String provider = services.get("provider"); - List providerList = null; - if (serviceProviderMap.containsKey(service)) { - providerList = serviceProviderMap.get(service); - } else { - providerList = new ArrayList(); - } - providerList.add(provider); - serviceProviderMap.put(service, providerList); - } - } else if (Boolean.TRUE.equals(forNsx)) { - getServiceProviderMapForNsx(serviceProviderMap); - } - return serviceProviderMap; - } - - private void getServiceProviderMapForNsx(Map> serviceProviderMap) { - String routerProvider = Boolean.TRUE.equals(getForVpc()) ? VirtualRouterProvider.Type.VPCVirtualRouter.name() : - VirtualRouterProvider.Type.VirtualRouter.name(); - List unsupportedServices = new ArrayList<>(List.of("Vpn", "SecurityGroup", "Connectivity", - "Gateway", "BaremetalPxeService")); - List routerSupported = List.of("Dhcp", "Dns", "UserData"); - List allServices = Service.listAllServices().stream().map(Service::getName).collect(Collectors.toList()); - if (routerProvider.equals(VirtualRouterProvider.Type.VPCVirtualRouter.name())) { - unsupportedServices.add("Firewall"); - } else { - unsupportedServices.add("NetworkACL"); - } - for (String service : allServices) { - if (unsupportedServices.contains(service)) - continue; - if (routerSupported.contains(service)) - serviceProviderMap.put(service, List.of(routerProvider)); - else - serviceProviderMap.put(service, List.of(Network.Provider.Nsx.getName())); - if (!getNsxSupportsLbService()) { - serviceProviderMap.remove(Lb.getName()); - } - } - } - - public Map getServiceCapabilities(Service service) { - Map capabilityMap = null; - - if (serviceCapabilitystList != null && !serviceCapabilitystList.isEmpty()) { - capabilityMap = new HashMap(); - Collection serviceCapabilityCollection = serviceCapabilitystList.values(); - Iterator iter = serviceCapabilityCollection.iterator(); - while (iter.hasNext()) { - HashMap svcCapabilityMap = (HashMap) iter.next(); - Capability capability = null; - String svc = svcCapabilityMap.get("service"); - String capabilityName = svcCapabilityMap.get("capabilitytype"); - String capabilityValue = svcCapabilityMap.get("capabilityvalue"); - - if (capabilityName != null) { - capability = Capability.getCapability(capabilityName); - } - - if ((capability == null) || (capabilityName == null) || (capabilityValue == null)) { - throw new InvalidParameterValueException("Invalid capability:" + capabilityName + " capability value:" + capabilityValue); - } - - if (svc.equalsIgnoreCase(service.getName())) { - capabilityMap.put(capability, capabilityValue); - } else { - //throw new InvalidParameterValueException("Service is not equal ") - } - } - } - - return capabilityMap; - } - - public Map getDetails() { - if (details == null || details.isEmpty()) { - return null; - } - - Collection paramsCollection = details.values(); - Object objlist[] = paramsCollection.toArray(); - Map params = (Map) (objlist[0]); - for (int i = 1; i < objlist.length; i++) { - params.putAll((Map) (objlist[i])); - } - - return params; - } - - public String getServicePackageId() { - Map data = getDetails(); - if (data == null) - return null; - return data.get(NetworkOffering.Detail.servicepackageuuid + ""); - } - - public List getDomainIds() { - if (CollectionUtils.isNotEmpty(domainIds)) { - Set set = new LinkedHashSet<>(domainIds); - domainIds.clear(); - domainIds.addAll(set); - } - return domainIds; - } - - public List getZoneIds() { - if (CollectionUtils.isNotEmpty(zoneIds)) { - Set set = new LinkedHashSet<>(zoneIds); - zoneIds.clear(); - zoneIds.addAll(set); - } - return zoneIds; - } - - public Boolean getEnable() { - if (enable != null) { - return enable; - } - return false; - } - - public boolean getSpecifyAsNumber() { - return BooleanUtils.toBoolean(specifyAsNumber); - } - - public String getRoutingMode() { - return routingMode; - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// - @Override - public long getEntityOwnerId() { - return Account.ACCOUNT_ID_SYSTEM; - } @Override public void execute() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreatePhysicalNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreatePhysicalNetworkCmd.java index f4ce9483bfbd..097b8a5b5458 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreatePhysicalNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/CreatePhysicalNetworkCmd.java @@ -75,7 +75,7 @@ public class CreatePhysicalNetworkCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.ISOLATION_METHODS, type = CommandType.LIST, collectionType = CommandType.STRING, - description = "The isolation method for the physical Network[VLAN/L3/GRE]") + description = "The isolation method for the physical Network[VLAN/VXLAN/GRE/STT/BCF_SEGMENT/SSP/ODL/L3VPN/VCS/NSX/NETRIS]") private List isolationMethods; @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the physical Network") @@ -148,7 +148,7 @@ public String getEventDescription() { @Override public void execute() { - CallContext.current().setEventDetails("Physical Network ID: " + getEntityId()); + CallContext.current().setEventDetails("Physical Network ID: " + getEntityUuid()); PhysicalNetwork result = _networkService.getCreatedPhysicalNetwork(getEntityId()); if (result != null) { PhysicalNetworkResponse response = _responseGenerator.createPhysicalNetworkResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmd.java index 2df032c559c5..cc76b284e24a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmd.java @@ -82,7 +82,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Dedicating zone IPv4 subnet " + getId(); + return "Dedicating zone's IPv4 subnet with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java index e2ada4191a82..405bbb594edb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteGuestNetworkIpv6PrefixCmd.java @@ -63,7 +63,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting guest IPv6 prefix " + getId(); + return "Deleting guest IPv6 prefix with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmd.java index 28a646f9d036..f6b22f79dfc7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmd.java @@ -59,7 +59,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting guest IPv4 subnet " + getId(); + return "Deleting guest IPv4 subnet with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmd.java index 222bc1bad98d..0ff2a9ad70b8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmd.java @@ -59,7 +59,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting zone IPv4 subnet " + getId(); + return "Deleting zone IPv4 subnet with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteManagementNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteManagementNetworkIpRangeCmd.java index 41cf5e518b34..1e69aaa6c440 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteManagementNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteManagementNetworkIpRangeCmd.java @@ -100,7 +100,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting management ip range from " + getStartIp() + " to " + getEndIp() + " of Pod: " + getPodId(); + return "Deleting management IP range from " + getStartIp() + " to " + getEndIp() + " from Pod: " + getResourceUuid(ApiConstants.POD_ID); } @Override @@ -116,7 +116,7 @@ public void execute() { logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } catch (Exception e) { - logger.warn("Failed to delete management ip range from " + getStartIp() + " to " + getEndIp() + " of Pod: " + getPodId(), e); + logger.warn("Failed to delete management ip range from {} to {} of Pod: {}", getStartIp(), getEndIp(), getPodId(), e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkServiceProviderCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkServiceProviderCmd.java index 23d14966c49b..2573e92b9860 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkServiceProviderCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteNetworkServiceProviderCmd.java @@ -91,7 +91,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting Physical network ServiceProvider: " + getId(); + return "Deleting Physical network ServiceProvider with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeletePhysicalNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeletePhysicalNetworkCmd.java index 70c35716b657..9994e8e391d7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeletePhysicalNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeletePhysicalNetworkCmd.java @@ -65,7 +65,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Physical Network Id: " + id); + CallContext.current().setEventDetails("Physical Network Id: " + getResourceUuid(ApiConstants.ID)); boolean result = _networkService.deletePhysicalNetwork(getId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -77,7 +77,7 @@ public void execute() { @Override public String getEventDescription() { - return "Deleting Physical network: " + getId(); + return "Deleting Physical network with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteStorageNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteStorageNetworkIpRangeCmd.java index d12135cc60c4..dcab38561408 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteStorageNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/DeleteStorageNetworkIpRangeCmd.java @@ -64,7 +64,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting storage ip range " + getId(); + return "Deleting storage IP range with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -75,7 +75,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); } catch (Exception e) { - logger.warn("Failed to delete storage network ip range " + getId(), e); + logger.warn("Failed to delete storage network ip range {}", getId(), e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListStorageNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListStorageNetworkIpRangeCmd.java index c269be933735..3e32bed3d500 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListStorageNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ListStorageNetworkIpRangeCmd.java @@ -97,7 +97,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE response.setResponseName(getCommandName()); this.setResponseObject(response); } catch (Exception e) { - logger.warn("Failed to list storage Network IP range for rangeId=" + getRangeId() + " podId=" + getPodId() + " zoneId=" + getZoneId()); + logger.warn("Failed to list storage Network IP range for rangeId={} podId={} zoneId={}", getRangeId(), getPodId(), getZoneId()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateNetworkCmd.java index 8ac9c8da691d..ad78bd3b406c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateNetworkCmd.java @@ -115,7 +115,7 @@ public void execute() { @Override public String getEventDescription() { - StringBuilder eventMsg = new StringBuilder("Migrating network: " + getId()); + String description = "Migrating Network with ID: " + getResourceUuid(ApiConstants.NETWORK_ID); if (getNetworkOfferingId() != null) { Network network = _networkService.getNetwork(getId()); if (network == null) { @@ -128,11 +128,11 @@ public String getEventDescription() { throw new InvalidParameterValueException("Network offering id supplied is invalid"); } - eventMsg.append(". Original network offering id: " + oldOff.getUuid() + ", new network offering id: " + newOff.getUuid()); + description += ". Original Network Offering id: " + oldOff.getUuid() + ", new Network Offering id: " + newOff.getUuid(); } } - return eventMsg.toString(); + return description; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateVPCCmd.java index edef1f846ed7..2973fea33c6a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/MigrateVPCCmd.java @@ -120,7 +120,7 @@ public void execute() { } @Override - public String getEventDescription() { return "Migrating VPC : " + getId() + " to new VPC offering (" + vpcOfferingId + ")"; } + public String getEventDescription() { return "Migrating VPC with ID: " + getResourceUuid(ApiConstants.VPC_ID) + " to new VPC offering with ID: " + getResourceUuid(ApiConstants.VPC_OFF_ID);} @Override public String getEventType() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java new file mode 100644 index 000000000000..9b42be137314 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/NetworkOfferingBaseCmd.java @@ -0,0 +1,494 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.network; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.Network; +import com.cloud.network.VirtualRouterProvider; +import com.cloud.offering.NetworkOffering; +import com.cloud.user.Account; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + +import static com.cloud.network.Network.Service.Dhcp; +import static com.cloud.network.Network.Service.Dns; +import static com.cloud.network.Network.Service.Firewall; +import static com.cloud.network.Network.Service.Lb; +import static com.cloud.network.Network.Service.NetworkACL; +import static com.cloud.network.Network.Service.PortForwarding; +import static com.cloud.network.Network.Service.SourceNat; +import static com.cloud.network.Network.Service.StaticNat; +import static com.cloud.network.Network.Service.UserData; + +import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNsxWithoutLb; +import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisNatted; +import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisRouted; + +public abstract class NetworkOfferingBaseCmd extends BaseCmd { + + public abstract String getGuestIpType(); + public abstract String getTraffictype(); + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "The name of the network offering") + private String networkOfferingName; + + @Parameter(name = ApiConstants.DISPLAY_TEXT, type = CommandType.STRING, description = "The display text of the network offering, defaults to the value of 'name'.") + private String displayText; + + @Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "The tags for the network offering.", length = 4096) + private String tags; + + @Parameter(name = ApiConstants.SPECIFY_VLAN, type = CommandType.BOOLEAN, description = "True if network offering supports VLANs") + private Boolean specifyVlan; + + @Parameter(name = ApiConstants.AVAILABILITY, type = CommandType.STRING, description = "The availability of network offering. The default value is Optional. " + + " Another value is Required, which will make it as the default network offering for new networks ") + private String availability; + + @Parameter(name = ApiConstants.NETWORKRATE, type = CommandType.INTEGER, description = "Data transfer rate in megabits per second allowed") + private Integer networkRate; + + @Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN, description = "True if the network offering is IP conserve mode enabled") + private Boolean conserveMode; + + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, + type = CommandType.UUID, + entityType = ServiceOfferingResponse.class, + description = "The service offering ID used by virtual router provider") + private Long serviceOfferingId; + + @Parameter(name = ApiConstants.INTERNET_PROTOCOL, + type = CommandType.STRING, + description = "The internet protocol of network offering. Options are IPv4 and dualstack. Default is IPv4. dualstack will create a network offering that supports both IPv4 and IPv6", + since = "4.17.0") + private String internetProtocol; + + @Parameter(name = ApiConstants.SUPPORTED_SERVICES, + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = "Services supported by the network offering") + private List supportedServices; + + @Parameter(name = ApiConstants.SERVICE_PROVIDER_LIST, + type = CommandType.MAP, + description = "Provider to service mapping. If not specified, the provider for the service will be mapped to the default provider on the physical network") + private Map serviceProviderList; + + @Parameter(name = ApiConstants.SERVICE_CAPABILITY_LIST, type = CommandType.MAP, description = "Desired service capabilities as part of network offering") + private Map serviceCapabilitiesList; + + @Parameter(name = ApiConstants.SPECIFY_IP_RANGES, + type = CommandType.BOOLEAN, + description = "True if network offering supports specifying ip ranges; defaulted to false if not specified") + private Boolean specifyIpRanges; + + @Parameter(name = ApiConstants.IS_PERSISTENT, + type = CommandType.BOOLEAN, + description = "True if network offering supports persistent networks; defaulted to false if not specified") + private Boolean isPersistent; + + @Parameter(name = ApiConstants.FOR_VPC, + type = CommandType.BOOLEAN, + description = "True if network offering is meant to be used for VPC, false otherwise.") + private Boolean forVpc; + + @Deprecated + @Parameter(name = ApiConstants.FOR_NSX, + type = CommandType.BOOLEAN, + description = "true if network offering is meant to be used for NSX, false otherwise.", + since = "4.20.0") + private Boolean forNsx; + + @Parameter(name = ApiConstants.PROVIDER, + type = CommandType.STRING, + description = "Name of the provider providing the service", + since = "4.21.0") + private String provider; + + @Parameter(name = ApiConstants.NSX_SUPPORT_LB, + type = CommandType.BOOLEAN, + description = "True if network offering for NSX network offering supports Load balancer service.", + since = "4.20.0") + private Boolean nsxSupportsLbService; + + @Parameter(name = ApiConstants.NSX_SUPPORTS_INTERNAL_LB, + type = CommandType.BOOLEAN, + description = "True if network offering for NSX network offering supports Internal Load balancer service.", + since = "4.20.0") + private Boolean nsxSupportsInternalLbService; + + @Parameter(name = ApiConstants.NETWORK_MODE, + type = CommandType.STRING, + description = "Indicates the mode with which the network will operate. Valid option: NATTED or ROUTED", + since = "4.20.0") + private String networkMode; + + @Parameter(name = ApiConstants.FOR_TUNGSTEN, + type = CommandType.BOOLEAN, + description = "True if network offering is meant to be used for Tungsten-Fabric, false otherwise.") + private Boolean forTungsten; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.2.0", description = "Network offering details in key/value pairs." + + " Supported keys are internallbprovider/publiclbprovider with service provider as a value, and" + + " promiscuousmode/macaddresschanges/forgedtransmits with true/false as value to accept/reject the security settings if available for a nic/portgroup") + protected Map details; + + @Parameter(name = ApiConstants.EGRESS_DEFAULT_POLICY, + type = CommandType.BOOLEAN, + description = "True if guest network default egress policy is allow; false if default egress policy is deny") + private Boolean egressDefaultPolicy; + + @Parameter(name = ApiConstants.KEEPALIVE_ENABLED, + type = CommandType.BOOLEAN, + required = false, + description = "If true keepalive will be turned on in the loadbalancer. At the time of writing this has only an effect on haproxy; the mode http and httpclose options are unset in the haproxy conf file.") + private Boolean keepAliveEnabled; + + @Parameter(name = ApiConstants.MAX_CONNECTIONS, + type = CommandType.INTEGER, + description = "Maximum number of concurrent connections supported by the Network offering") + private Integer maxConnections; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = DomainResponse.class, + description = "The ID of the containing domain(s), null for public offerings") + private List domainIds; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.LIST, + collectionType = CommandType.UUID, + entityType = ZoneResponse.class, + description = "The ID of the containing zone(s), null for public offerings", + since = "4.13") + private List zoneIds; + + @Parameter(name = ApiConstants.ENABLE, + type = CommandType.BOOLEAN, + description = "Set to true if the offering is to be enabled during creation. Default is false", + since = "4.16") + private Boolean enable; + + @Parameter(name = ApiConstants.SPECIFY_AS_NUMBER, type = CommandType.BOOLEAN, since = "4.20.0", + description = "true if network offering supports choosing AS number") + private Boolean specifyAsNumber; + + @Parameter(name = ApiConstants.ROUTING_MODE, + type = CommandType.STRING, + since = "4.20.0", + description = "the routing mode for the network offering. Supported types are: Static or Dynamic.") + private String routingMode; + + private Map sourceDetailsMap; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getNetworkOfferingName() { + return networkOfferingName; + } + + public String getDisplayText() { + return StringUtils.isEmpty(displayText) ? networkOfferingName : displayText; + } + + public String getTags() { + return tags; + } + + public Boolean getSpecifyVlan() { + return specifyVlan == null ? false : specifyVlan; + } + + public String getAvailability() { + return availability == null ? NetworkOffering.Availability.Optional.toString() : availability; + } + + public Integer getNetworkRate() { + return networkRate; + } + + public Long getServiceOfferingId() { + return serviceOfferingId; + } + + public boolean isExternalNetworkProvider() { + return Arrays.asList("NSX", "Netris").stream() + .anyMatch(s -> provider != null && s.equalsIgnoreCase(provider)); + } + + public boolean isForNsx() { + return provider != null && provider.equalsIgnoreCase("NSX"); + } + + public boolean isForNetris() { + return provider != null && provider.equalsIgnoreCase("Netris"); + } + + public String getProvider() { + return provider; + } + + public List getSupportedServices() { + if (!isExternalNetworkProvider()) { + return supportedServices == null ? new ArrayList() : supportedServices; + } else { + List services = new ArrayList<>(List.of( + Dhcp.getName(), + Dns.getName(), + UserData.getName() + )); + if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) { + services.addAll(Arrays.asList( + StaticNat.getName(), + SourceNat.getName(), + PortForwarding.getName())); + } + if (getNsxSupportsLbService() || (provider != null && isNetrisNatted(getProvider(), getNetworkMode()))) { + services.add(Lb.getName()); + } + if (Boolean.TRUE.equals(forVpc)) { + services.add(NetworkACL.getName()); + } else { + services.add(Firewall.getName()); + } + return services; + } + } + + public String getInternetProtocol() { + return internetProtocol; + } + + public Boolean getSpecifyIpRanges() { + return specifyIpRanges == null ? false : specifyIpRanges; + } + + public Boolean getConserveMode() { + if (conserveMode == null) { + return true; + } + return conserveMode; + } + + public Boolean getIsPersistent() { + return isPersistent == null ? false : isPersistent; + } + + public Boolean getForVpc() { + return forVpc; + } + + public String getNetworkMode() { + return networkMode; + } + + public boolean getNsxSupportsLbService() { + return BooleanUtils.isTrue(nsxSupportsLbService); + } + + public boolean getNsxSupportsInternalLbService() { + return BooleanUtils.isTrue(nsxSupportsInternalLbService); + } + + public Boolean getForTungsten() { + return forTungsten; + } + + public Boolean getEgressDefaultPolicy() { + if (egressDefaultPolicy == null) { + return true; + } + return egressDefaultPolicy; + } + + public Boolean getKeepAliveEnabled() { + return keepAliveEnabled; + } + + public Integer getMaxconnections() { + return maxConnections; + } + + public Map> getServiceProviders() { + Map> serviceProviderMap = new HashMap<>(); + if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isExternalNetworkProvider()) { + Collection servicesCollection = serviceProviderList.values(); + Iterator iter = servicesCollection.iterator(); + while (iter.hasNext()) { + HashMap services = (HashMap) iter.next(); + String service = services.get("service"); + String provider = services.get("provider"); + List providerList = null; + if (serviceProviderMap.containsKey(service)) { + providerList = serviceProviderMap.get(service); + } else { + providerList = new ArrayList(); + } + providerList.add(provider); + serviceProviderMap.put(service, providerList); + } + } else if (isExternalNetworkProvider()) { + getServiceProviderMapForExternalProvider(serviceProviderMap, Network.Provider.getProvider(provider).getName()); + } + return serviceProviderMap; + } + + private void getServiceProviderMapForExternalProvider(Map> serviceProviderMap, String provider) { + String routerProvider = Boolean.TRUE.equals(getForVpc()) ? VirtualRouterProvider.Type.VPCVirtualRouter.name() : + VirtualRouterProvider.Type.VirtualRouter.name(); + List unsupportedServices = new ArrayList<>(List.of("Vpn", "Gateway", "SecurityGroup", "Connectivity", "BaremetalPxeService")); + List routerSupported = List.of("Dhcp", "Dns", "UserData"); + List allServices = Network.Service.listAllServices().stream().map(Network.Service::getName).collect(Collectors.toList()); + if (routerProvider.equals(VirtualRouterProvider.Type.VPCVirtualRouter.name())) { + unsupportedServices.add("Firewall"); + } else { + unsupportedServices.add("NetworkACL"); + } + for (String service : allServices) { + if (unsupportedServices.contains(service)) + continue; + if (routerSupported.contains(service)) + serviceProviderMap.put(service, List.of(routerProvider)); + else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) || NetworkACL.getName().equalsIgnoreCase(service)) { + serviceProviderMap.put(service, List.of(provider)); + } + if (isNsxWithoutLb(getProvider(), getNsxSupportsLbService()) || isNetrisRouted(getProvider(), getNetworkMode())) { + serviceProviderMap.remove(Lb.getName()); + } + } + } + + public Map getServiceCapabilities(Network.Service service) { + Map capabilityMap = null; + + if (serviceCapabilitiesList != null && !serviceCapabilitiesList.isEmpty()) { + capabilityMap = new HashMap(); + Collection serviceCapabilityCollection = serviceCapabilitiesList.values(); + Iterator iter = serviceCapabilityCollection.iterator(); + while (iter.hasNext()) { + HashMap svcCapabilityMap = (HashMap) iter.next(); + Network.Capability capability = null; + String svc = svcCapabilityMap.get("service"); + String capabilityName = svcCapabilityMap.get("capabilitytype"); + String capabilityValue = svcCapabilityMap.get("capabilityvalue"); + + if (capabilityName != null) { + capability = Network.Capability.getCapability(capabilityName); + } + + if ((capability == null) || (capabilityName == null) || (capabilityValue == null)) { + throw new InvalidParameterValueException("Invalid capability:" + capabilityName + " capability value:" + capabilityValue); + } + + if (svc.equalsIgnoreCase(service.getName())) { + capabilityMap.put(capability, capabilityValue); + } else { + //throw new InvalidParameterValueException("Service is not equal ") + } + } + } + + return capabilityMap; + } + + public Map getDetails() { + if (details == null || details.isEmpty()) { + return sourceDetailsMap; + } + Collection paramsCollection = details.values(); + Object objlist[] = paramsCollection.toArray(); + Map params = (Map) (objlist[0]); + for (int i = 1; i < objlist.length; i++) { + params.putAll((Map) (objlist[i])); + } + + return params; + } + + public String getServicePackageId() { + Map data = getDetails(); + if (data == null) + return null; + return data.get(NetworkOffering.Detail.servicepackageuuid + ""); + } + + public List getDomainIds() { + if (CollectionUtils.isNotEmpty(domainIds)) { + Set set = new LinkedHashSet<>(domainIds); + domainIds.clear(); + domainIds.addAll(set); + } + return domainIds; + } + + public List getZoneIds() { + if (CollectionUtils.isNotEmpty(zoneIds)) { + Set set = new LinkedHashSet<>(zoneIds); + zoneIds.clear(); + zoneIds.addAll(set); + } + return zoneIds; + } + + public Boolean getEnable() { + if (enable != null) { + return enable; + } + return false; + } + + public boolean getSpecifyAsNumber() { + return BooleanUtils.toBoolean(specifyAsNumber); + } + + public String getRoutingMode() { + return routingMode; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedGuestVlanRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedGuestVlanRangeCmd.java index 632ff9c6ac7f..56d042719f68 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedGuestVlanRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedGuestVlanRangeCmd.java @@ -72,7 +72,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Releasing a dedicated guest vlan range."; + return "Releasing dedicated guest VLAN range with ID: " + getResourceUuid(ApiConstants.ID); } // /////////////////////////////////////////////////// @@ -81,7 +81,7 @@ public String getEventDescription() { @Override public void execute() { - CallContext.current().setEventDetails("Dedicated guest vlan range Id: " + id); + CallContext.current().setEventDetails("Dedicated guest VLAN range ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _networkService.releaseDedicatedGuestVlanRange(getId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmd.java index 3e151b9b58f4..a5e763c0cb00 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmd.java @@ -59,7 +59,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Releasing a dedicated zone IPv4 subnet " + getId(); + return "Releasing dedicated zone IPv4 subnet with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmd.java index da7a23f50d9c..db5daa505bee 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmd.java @@ -69,7 +69,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating zone IPv4 subnet " + getId(); + return "Updating zone IPv4 subnet with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java index 8910966ba2e3..df9f6ad0664d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.network; -import java.util.ArrayList; import java.util.List; import org.apache.cloudstack.api.APICommand; @@ -26,18 +25,16 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.NetworkOfferingResponse; -import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; -import com.cloud.exception.InvalidParameterValueException; + import com.cloud.offering.NetworkOffering; import com.cloud.user.Account; @APICommand(name = "updateNetworkOffering", description = "Updates a network offering.", responseObject = NetworkOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateNetworkOfferingCmd extends BaseCmd { +public class UpdateNetworkOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -130,63 +127,11 @@ public String getTags() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_configService.getNetworkOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _configService::getNetworkOfferingDomains, "network offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create network offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_configService.getNetworkOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _configService::getNetworkOfferingZones, "network offering"); } ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkServiceProviderCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkServiceProviderCmd.java index db57abad2489..e0ce0aade1ee 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkServiceProviderCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateNetworkServiceProviderCmd.java @@ -100,7 +100,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating physical network ServiceProvider: " + getId(); + return "Updating Physical Network ServiceProvider with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePhysicalNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePhysicalNetworkCmd.java index a9ad46fdd78f..6a6264e418ce 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePhysicalNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePhysicalNetworkCmd.java @@ -98,7 +98,7 @@ public void execute() { @Override public String getEventDescription() { - return "Updating Physical network: " + getId(); + return "Updating Physical Network with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePodManagementNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePodManagementNetworkIpRangeCmd.java index 6f90a070f0d1..0dfa83a68289 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePodManagementNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdatePodManagementNetworkIpRangeCmd.java @@ -113,7 +113,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating pod management IP range " + getNewStartIP() + "-" + getNewEndIP() + " of Pod: " + getPodId(); + return "Updating pod management IP range " + getNewStartIP() + "-" + getNewEndIP() + " of Pod: " + getResourceUuid(ApiConstants.POD_ID); } @Override @@ -139,7 +139,7 @@ public void execute() { logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } catch (Exception e) { - logger.warn("Failed to update pod management IP range " + getNewStartIP() + "-" + getNewEndIP() + " of Pod: " + getPodId(), e); + logger.warn("Failed to update pod management IP range {}-{} of Pod: {}", getNewStartIP(), getNewEndIP(), getPodId(), e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateStorageNetworkIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateStorageNetworkIpRangeCmd.java index a1f4d2ed1002..978e94a783a9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateStorageNetworkIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/UpdateStorageNetworkIpRangeCmd.java @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Update storage ip range " + getId() + " [StartIp=" + getStartIp() + ", EndIp=" + getEndIp() + ", vlan=" + getVlan() + ", netmask=" + getNetmask() + ']'; + return "Updating storage IP range " + getResourceUuid(ApiConstants.ID) + " [StartIp=" + getStartIp() + ", EndIp=" + getEndIp() + ", VLAN=" + getVlan() + ", netmask=" + getNetmask() + ']'; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmd.java index 1d6bffca342e..3c58cbb3c532 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmd.java @@ -80,7 +80,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Changing Bgp Peers for network " + getNetworkId(); + return "Changing BGP Peers for Network with ID: " + getResourceUuid(ApiConstants.NETWORK_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmd.java index 0c89f3f1d43c..8784f0672790 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmd.java @@ -80,7 +80,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Changing Bgp Peers for VPC " + getVpcId(); + return "Changing BGP Peers for VPC with ID: " + getResourceUuid(ApiConstants.VPC_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmd.java index 80642124938a..f1d9b6723091 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmd.java @@ -145,7 +145,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating Bgp Peer " + getAsNumber() + " for zone=" + getZoneId(); + return "Creating BGP Peer " + getAsNumber() + " for zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmd.java index ec3d0ea11629..f1ef963e9872 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmd.java @@ -82,7 +82,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Dedicating Bgp Peer " + getId(); + return "Dedicating BGP Peer with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmd.java index a01711efa44f..a412e91bc48e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmd.java @@ -59,7 +59,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting Bgp Peer " + getId(); + return "Deleting BGP Peer with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmd.java index 92610c233ef0..c754d443c051 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmd.java @@ -59,7 +59,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Releasing a dedicated Bgp Peer " + getId(); + return "Releasing dedicated BGP Peer with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmd.java index ae44330ea033..f45c1ee5a2f3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmd.java @@ -120,7 +120,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating Bgp Peer " + getId(); + return "Updating BGP Peer with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneDiskOfferingCmd.java new file mode 100644 index 000000000000..8d822be203ad --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneDiskOfferingCmd.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.offering; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DiskOfferingResponse; + +import com.cloud.offering.DiskOffering; + +@APICommand(name = "cloneDiskOffering", + description = "Clones a disk offering. All parameters from createDiskOffering are available. If not specified, values will be copied from the source offering.", + responseObject = DiskOfferingResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.DomainAdmin}) +public class CloneDiskOfferingCmd extends CreateDiskOfferingCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.SOURCE_OFFERING_ID, + type = BaseCmd.CommandType.UUID, + entityType = DiskOfferingResponse.class, + required = true, + description = "The ID of the source disk offering to clone from") + private Long sourceOfferingId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getSourceOfferingId() { + return sourceOfferingId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + DiskOffering result = _configService.cloneDiskOffering(this); + if (result != null) { + DiskOfferingResponse response = _responseGenerator.createDiskOfferingResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone disk offering"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmd.java new file mode 100644 index 000000000000..d01ca4c195da --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmd.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.offering; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; + +import com.cloud.offering.ServiceOffering; + +@APICommand(name = "cloneServiceOffering", + description = "Clones a service offering. All parameters from createServiceOffering are available. If not specified, values will be copied from the source offering.", + responseObject = ServiceOfferingResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.DomainAdmin}) +public class CloneServiceOfferingCmd extends CreateServiceOfferingCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.SOURCE_OFFERING_ID, + type = CommandType.UUID, + entityType = ServiceOfferingResponse.class, + required = true, + description = "The ID of the source service offering to clone from") + private Long sourceOfferingId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getSourceOfferingId() { + return sourceOfferingId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + + @Override + public void execute() { + try { + ServiceOffering result = _configService.cloneServiceOffering(this); + if (result != null) { + ServiceOfferingResponse response = _responseGenerator.createServiceOfferingResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone service offering"); + } + } catch (com.cloud.exception.InvalidParameterValueException e) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, e.getMessage()); + } catch (com.cloud.utils.exception.CloudRuntimeException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java index 38ccee70b574..e1ff90e4bd89 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateDiskOfferingCmd.java @@ -150,8 +150,7 @@ public class CreateDiskOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING, - required = false, - description = "The cache mode to use for this disk offering. none, writeback or writethrough", + description = "The cache mode to use for this disk offering. none, writeback, writethrough or hypervisor default. If the hypervisor default cache mode is used on other hypervisors than KVM, it will fall back to none cache mode", since = "4.14") private String cacheMode; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java index 0c6671caf029..4363d6861ba1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmd.java @@ -29,14 +29,17 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; import org.apache.cloudstack.api.response.VsphereStoragePoliciesResponse; import org.apache.cloudstack.api.response.ZoneResponse; -import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.cloudstack.vm.lease.VMLeaseManager; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.collections.CollectionUtils; import com.cloud.exception.InvalidParameterValueException; import com.cloud.offering.ServiceOffering; @@ -186,8 +189,7 @@ public class CreateServiceOfferingCmd extends BaseCmd { @Parameter(name = ApiConstants.CACHE_MODE, type = CommandType.STRING, - required = false, - description = "The cache mode to use for this disk offering. none, writeback or writethrough", + description = "The cache mode to use for this disk offering. none, writeback, writethrough or hypervisor default. If the hypervisor default cache mode is used on other hypervisors than KVM, it will fall back to none cache mode", since = "4.14") private String cacheMode; @@ -251,7 +253,40 @@ public class CreateServiceOfferingCmd extends BaseCmd { since="4.20") private Boolean purgeResources; + @Parameter(name = ApiConstants.INSTANCE_LEASE_DURATION, + type = CommandType.INTEGER, + description = "Number of days instance is leased for.", + since = "4.21.0") + private Integer leaseDuration; + + @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0", + description = "Lease expiry action, valid values are STOP and DESTROY") + private String leaseExpiryAction; + @Parameter(name = ApiConstants.VGPU_PROFILE_ID, + type = CommandType.UUID, + entityType = VgpuProfileResponse.class, + description = "the ID of the vGPU profile to which service offering should be mapped", + since = "4.21") + private Long vgpuProfileId; + + @Parameter(name = ApiConstants.GPU_COUNT, + type = CommandType.INTEGER, + description = "Count of GPUs to be used with this service offering. This is applicable only when passed with vGPU profile.", + since = "4.21") + private Integer gpuCount; + + @Parameter(name = ApiConstants.GPU_DISPLAY, + type = CommandType.BOOLEAN, + description = "Whether to enable GPU display for this service offering. This is applicable only when passed with vGPU profile. Defaults to false.", + since = "4.21") + private Boolean gpuDisplay; + + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, + type = CommandType.MAP, + description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue", + since = "4.21.0") + private Map externalDetails; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -359,9 +394,15 @@ public Map getDetails() { } } } + + detailsMap.putAll(getExternalDetails()); return detailsMap; } + public Map getExternalDetails() { + return convertExternalDetailsToMap(externalDetails); + } + public Long getRootDiskSize() { return rootDiskSize; } @@ -487,10 +528,38 @@ public boolean getEncryptRoot() { return false; } + public VMLeaseManager.ExpiryAction getLeaseExpiryAction() { + if (StringUtils.isBlank(leaseExpiryAction)) { + return null; + } + VMLeaseManager.ExpiryAction action = EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, leaseExpiryAction); + if (action == null) { + throw new InvalidParameterValueException("Invalid value configured for leaseexpiryaction, valid values are: " + + com.cloud.utils.EnumUtils.listValues(VMLeaseManager.ExpiryAction.values())); + } + return action; + } + + public Integer getLeaseDuration() { + return leaseDuration; + } + public boolean isPurgeResources() { return Boolean.TRUE.equals(purgeResources); } + public Long getVgpuProfileId() { + return vgpuProfileId; + } + + public Integer getGpuCount() { + return gpuCount; + } + + public Boolean getGpuDisplay() { + return Boolean.TRUE.equals(gpuDisplay); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/IsAccountAllowedToCreateOfferingsWithTagsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/IsAccountAllowedToCreateOfferingsWithTagsCmd.java index fcd6b03d3e59..4b1cd2ff7255 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/IsAccountAllowedToCreateOfferingsWithTagsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/IsAccountAllowedToCreateOfferingsWithTagsCmd.java @@ -26,7 +26,8 @@ import org.apache.cloudstack.api.response.IsAccountAllowedToCreateOfferingsWithTagsResponse; @APICommand(name = "isAccountAllowedToCreateOfferingsWithTags", description = "Return true if the specified account is allowed to create offerings with tags.", - responseObject = IsAccountAllowedToCreateOfferingsWithTagsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) + responseObject = IsAccountAllowedToCreateOfferingsWithTagsResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + httpMethod = "GET") public class IsAccountAllowedToCreateOfferingsWithTagsCmd extends BaseCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "Account UUID", required = true) diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java index c93b5d41a1c5..4d48327eeb77 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateDiskOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; -import java.util.ArrayList; import java.util.List; import com.cloud.offering.DiskOffering.State; @@ -27,19 +26,18 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; import com.cloud.exception.InvalidParameterValueException; import com.cloud.offering.DiskOffering; import com.cloud.user.Account; @APICommand(name = "updateDiskOffering", description = "Updates a disk offering.", responseObject = DiskOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateDiskOfferingCmd extends BaseCmd { +public class UpdateDiskOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -152,63 +150,11 @@ public Boolean getDisplayOffering() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_configService.getDiskOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _configService::getDiskOfferingDomains, "disk offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create disk offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_configService.getDiskOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _configService::getDiskOfferingZones, "disk offering"); } public String getTags() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java index 26c7d87ab45c..8e37499c95ed 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/offering/UpdateServiceOfferingCmd.java @@ -16,8 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.admin.offering; -import java.util.ArrayList; import java.util.List; +import java.util.Map; import com.cloud.offering.ServiceOffering.State; import org.apache.cloudstack.api.APICommand; @@ -27,19 +27,18 @@ import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; import com.cloud.exception.InvalidParameterValueException; import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; @APICommand(name = "updateServiceOffering", description = "Updates a service offering.", responseObject = ServiceOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateServiceOfferingCmd extends BaseCmd { +public class UpdateServiceOfferingCmd extends BaseCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -95,6 +94,20 @@ public class UpdateServiceOfferingCmd extends BaseCmd { since="4.20") private Boolean purgeResources; + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, + type = CommandType.MAP, + description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue", + since = "4.21.0") + private Map externalDetails; + + @Parameter(name = ApiConstants.CLEAN_UP_EXTERNAL_DETAILS, + type = CommandType.BOOLEAN, + description = "Optional boolean field, which indicates if external details should be cleaned up or not " + + "(If set to true, external details removed for this offering, externaldetails field ignored; " + + "if false or not set, no action)", + since = "4.22.0") + protected Boolean cleanupExternalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -116,63 +129,11 @@ public Integer getSortKey() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_configService.getServiceOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _configService::getServiceOfferingDomains, "service offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create service offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_configService.getServiceOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _configService::getServiceOfferingZones, "service offering"); } public String getStorageTags() { @@ -195,6 +156,14 @@ public boolean isPurgeResources() { return Boolean.TRUE.equals(purgeResources); } + public Map getExternalDetails() { + return convertExternalDetailsToMap(externalDetails); + } + + public boolean isCleanupExternalDetails() { + return Boolean.TRUE.equals(cleanupExternalDetails); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java index b22697768681..b9a729bc1b77 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/ChangeOutOfBandManagementPasswordCmd.java @@ -70,7 +70,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find host by ID: " + getHostId()); } - CallContext.current().setEventDetails("Host Id: " + host.getId() + " Password: " + getPassword().charAt(0) + "****"); + CallContext.current().setEventDetails("Host ID: " + host.getUuid() + " Password: " + getPassword().charAt(0) + "****"); CallContext.current().putContextParameter(Host.class, host.getUuid()); final OutOfBandManagementResponse response = outOfBandManagementService.changePassword(host, getPassword()); @@ -101,7 +101,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "change out-of-band management password for host: " + getHostId(); + return "Changing out-of-band management password for host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java index 26b0eac6be4a..5b0f3a802071 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForClusterCmd.java @@ -70,7 +70,7 @@ final public void execute() throws ResourceUnavailableException, InsufficientCap OutOfBandManagementResponse response = outOfBandManagementService.disableOutOfBandManagement(cluster); - CallContext.current().setEventDetails("Cluster Id:" + cluster.getId() + " out-of-band management enabled: false"); + CallContext.current().setEventDetails("Cluster ID:" + cluster.getUuid() + " out-of-band management enabled: false"); CallContext.current().putContextParameter(Cluster.class, cluster.getUuid()); response.setResponseName(getCommandName()); @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "disable out-of-band management password for cluster: " + getClusterId(); + return "Disabling out-of-band management password for cluster with ID: " + getResourceUuid(ApiConstants.CLUSTER_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java index 6c9b48ef28f7..c70622134531 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForHostCmd.java @@ -70,7 +70,7 @@ final public void execute() throws ResourceUnavailableException, InsufficientCap OutOfBandManagementResponse response = outOfBandManagementService.disableOutOfBandManagement(host); - CallContext.current().setEventDetails("Host Id:" + host.getId() + " out-of-band management enabled: false"); + CallContext.current().setEventDetails("Host ID:" + host.getUuid() + " out-of-band management enabled: false"); CallContext.current().putContextParameter(Host.class, host.getUuid()); response.setId(host.getUuid()); @@ -94,7 +94,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "disable out-of-band management password for host: " + getHostId(); + return "Disabling out-of-band management password for host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java index 4f262ca5c09f..2f2750580516 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/DisableOutOfBandManagementForZoneCmd.java @@ -70,7 +70,7 @@ final public void execute() throws ResourceUnavailableException, InsufficientCap OutOfBandManagementResponse response = outOfBandManagementService.disableOutOfBandManagement(zone); - CallContext.current().setEventDetails("Zone Id:" + zone.getId() + " out-of-band management enabled: false"); + CallContext.current().setEventDetails("Zone ID:" + zone.getUuid() + " out-of-band management enabled: false"); CallContext.current().putContextParameter(DataCenter.class, zone.getUuid()); response.setResponseName(getCommandName()); @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "disable out-of-band management password for zone: " + getZoneId(); + return "Disabling out-of-band management password for zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java index 6620f86907e9..4419ed18ee59 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForClusterCmd.java @@ -70,7 +70,7 @@ final public void execute() throws ResourceUnavailableException, InsufficientCap OutOfBandManagementResponse response = outOfBandManagementService.enableOutOfBandManagement(cluster); - CallContext.current().setEventDetails("Cluster Id:" + cluster.getId() + " out-of-band management enabled: true"); + CallContext.current().setEventDetails("Cluster ID:" + cluster.getUuid() + " out-of-band management enabled: true"); CallContext.current().putContextParameter(Cluster.class, cluster.getUuid()); response.setResponseName(getCommandName()); @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "enable out-of-band management password for cluster: " + getClusterId(); + return "Enabling out-of-band management password for cluster with ID: " + getResourceUuid(ApiConstants.CLUSTER_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java index 3cfee31f78a8..b3f2e8aaf821 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForHostCmd.java @@ -70,7 +70,7 @@ final public void execute() throws ResourceUnavailableException, InsufficientCap OutOfBandManagementResponse response = outOfBandManagementService.enableOutOfBandManagement(host); - CallContext.current().setEventDetails("Host Id:" + host.getId() + " out-of-band management enabled: true"); + CallContext.current().setEventDetails("Host ID:" + host.getUuid() + " out-of-band management enabled: true"); CallContext.current().putContextParameter(Host.class, host.getUuid()); response.setId(host.getUuid()); @@ -94,7 +94,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "enable out-of-band management password for host: " + getHostId(); + return "Enabling out-of-band management password for host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java index 99d2324b1b66..8f6eb2dc3500 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/EnableOutOfBandManagementForZoneCmd.java @@ -70,7 +70,7 @@ final public void execute() throws ResourceUnavailableException, InsufficientCap OutOfBandManagementResponse response = outOfBandManagementService.enableOutOfBandManagement(zone); - CallContext.current().setEventDetails("Zone Id:" + zone.getId() + " out-of-band management enabled: true"); + CallContext.current().setEventDetails("Zone ID:" + zone.getUuid() + " out-of-band management enabled: true"); CallContext.current().putContextParameter(DataCenter.class, zone.getUuid()); response.setResponseName(getCommandName()); @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "enable out-of-band management password for zone: " + getZoneId(); + return "Enabling out-of-band management password for zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java index c63b03aad1b8..bba3fee6ceca 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/outofbandmanagement/IssueOutOfBandManagementPowerActionCmd.java @@ -75,7 +75,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE } final PowerOperation powerOperation = PowerOperation.valueOf(getPowerAction()); - CallContext.current().setEventDetails("Host Id: " + host.getId() + " Action: " + powerOperation.toString()); + CallContext.current().setEventDetails("Host ID: " + host.getUuid() + " Action: " + powerOperation.toString()); CallContext.current().putContextParameter(Host.class, host.getUuid()); final OutOfBandManagementResponse response = outOfBandManagementService.executePowerOperation(host, powerOperation, getActionTimeout()); @@ -107,7 +107,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "issue out-out-band management power action: " + getPowerAction() + " on host: " + getHostId(); + return "Issuing out-out-band management power action: " + getPowerAction() + " to host: " + getResourceUuid(ApiConstants.HOST_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/CreatePodCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/CreatePodCmd.java index d693c756f2d2..6a178b64d876 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/CreatePodCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/CreatePodCmd.java @@ -30,6 +30,8 @@ import com.cloud.dc.Pod; import com.cloud.user.Account; +import java.util.List; + @APICommand(name = "createPod", description = "Creates a new Pod.", responseObject = PodResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class CreatePodCmd extends BaseCmd { @@ -63,6 +65,12 @@ public class CreatePodCmd extends BaseCmd { @Parameter(name = ApiConstants.ALLOCATION_STATE, type = CommandType.STRING, description = "Allocation state of this Pod for allocation of new resources") private String allocationState; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, + type = CommandType.LIST, collectionType = CommandType.STRING, + description = "comma separated list of storage access groups for the hosts in the pod", + since = "4.21.0") + private List storageAccessGroups; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -95,6 +103,10 @@ public String getAllocationState() { return allocationState; } + public List getStorageAccessGroups() { + return storageAccessGroups; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -111,7 +123,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - Pod result = _configService.createPod(getZoneId(), getPodName(), getStartIp(), getEndIp(), getGateway(), getNetmask(), getAllocationState()); + Pod result = _configService.createPod(getZoneId(), getPodName(), getStartIp(), getEndIp(), getGateway(), getNetmask(), getAllocationState(), getStorageAccessGroups()); if (result != null) { PodResponse response = _responseGenerator.createPodResponse(result, false); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/ListPodsByCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/ListPodsByCmd.java index 8dbf5bcf6d64..a19bc3caf8b5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/ListPodsByCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/pod/ListPodsByCmd.java @@ -55,6 +55,11 @@ public class ListPodsByCmd extends BaseListCmd { @Parameter(name = ApiConstants.SHOW_CAPACITIES, type = CommandType.BOOLEAN, description = "Flag to display the capacity of the pods") private Boolean showCapacities; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUP, type = CommandType.STRING, + description = "the name of the storage access group", + since = "4.21.0") + private String storageAccessGroup; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -79,6 +84,18 @@ public Boolean getShowCapacities() { return showCapacities; } + public String getStorageAccessGroup() { + return storageAccessGroup; + } + + public ListPodsByCmd() { + + } + + public ListPodsByCmd(String storageAccessGroup) { + this.storageAccessGroup = storageAccessGroup; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java index da7293ea2198..ac8fe66a8144 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/region/DeletePortableIpRangeCmd.java @@ -83,7 +83,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting a portable public ip range"; + return "Deleting a portable public ip range with ID: " + getResourceUuid(ApiConstants.ID) ; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListAlertTypesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListAlertTypesCmd.java index e7bfbdbc6254..dcd4f2c89ef2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListAlertTypesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListAlertTypesCmd.java @@ -16,7 +16,10 @@ // under the License. package org.apache.cloudstack.api.command.admin.resource; -import com.cloud.user.Account; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + import org.apache.cloudstack.alert.AlertService; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.BaseCmd; @@ -24,9 +27,7 @@ import org.apache.cloudstack.api.response.AlertTypeResponse; import org.apache.cloudstack.api.response.ListResponse; -import java.util.ArrayList; -import java.util.List; -import java.util.Set; +import com.cloud.user.Account; @APICommand(name = "listAlertTypes", description = "Lists all alerts types", responseObject = AlertResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -43,7 +44,8 @@ public void execute() { ListResponse response = new ListResponse<>(); List typeResponseList = new ArrayList<>(); for (AlertService.AlertType alertType : result) { - AlertTypeResponse alertResponse = new AlertTypeResponse(alertType.getType(), alertType.getName()); + AlertTypeResponse alertResponse = new AlertTypeResponse(alertType.getType(), alertType.getName(), + alertType.isRepetitionAllowed()); alertResponse.setObjectName("alerttype"); typeResponseList.add(alertResponse); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListCapacityCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListCapacityCmd.java index e0d57795b71f..ae98ca9dcd26 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListCapacityCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/ListCapacityCmd.java @@ -141,7 +141,6 @@ public int compare(CapacityResponse resp1, CapacityResponse resp2) { } }); - response.setResponses(capacityResponses); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/UploadCustomCertificateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/UploadCustomCertificateCmd.java index c5ae6890c3e5..7144bbaa6015 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/UploadCustomCertificateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/resource/UploadCustomCertificateCmd.java @@ -84,7 +84,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Uploading custom certificate to the db, and applying it to all the cpvms in the system"); + return ("Uploading custom certificate and applying it to all the CPVMs in the system"); } public static String getResultObjectName() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java index e40462149582..474e13de32f3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureOvsElementCmd.java @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "configuring ovs provider: " + id; + return "Configuring OVS provider with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -109,7 +109,7 @@ public Long getApiResourceId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Ovs element: " + id); + CallContext.current().setEventDetails("OVS element: " + getResourceUuid(ApiConstants.ID)); OvsProvider result = _service.get(0).configure(this); if (result != null) { OvsProviderResponse ovsResponse = _responseGenerator diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java index 7be41834cd5a..ba0110f014a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/ConfigureVirtualRouterElementCmd.java @@ -100,7 +100,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "configuring virtual router provider: " + id; + return "Configuring virtual router with provider that has ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -115,7 +115,7 @@ public Long getApiResourceId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Virtual router element: " + id); + CallContext.current().setEventDetails("Virtual router element ID: " + getResourceUuid(ApiConstants.ID)); VirtualRouterProvider result = _service.get(0).configure(this); if (result != null) { VirtualRouterProviderResponse routerResponse = _responseGenerator.createVirtualRouterProviderResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java index 7c17fd794da6..094e87ae0058 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/CreateVirtualRouterElementCmd.java @@ -98,7 +98,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Virtual router element Id: " + getEntityUuid()); + CallContext.current().setEventDetails("Virtual router element ID: " + getEntityUuid()); VirtualRouterProvider result = _service.get(0).getCreatedElement(getEntityId()); if (result != null) { VirtualRouterProviderResponse response = _responseGenerator.createVirtualRouterProviderResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/DestroyRouterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/DestroyRouterCmd.java index 66fc2eb79e6d..d8355cadb0a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/DestroyRouterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/DestroyRouterCmd.java @@ -74,7 +74,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "destroying router: " + this._uuidMgr.getUuid(VirtualMachine.class,getId()); + return "Destroying virtual router with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -90,7 +90,7 @@ public Long getApiResourceId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException { CallContext ctx = CallContext.current(); - ctx.setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class,getId())); + ctx.setEventDetails("Router ID: " + getResourceUuid(ApiConstants.ID)); VirtualRouter result = _routerService.destroyRouter(getId(), ctx.getCallingAccount(), ctx.getCallingUserId()); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/GetRouterHealthCheckResultsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/GetRouterHealthCheckResultsCmd.java index 6d4d62929ded..a302ebee1046 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/GetRouterHealthCheckResultsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/GetRouterHealthCheckResultsCmd.java @@ -87,7 +87,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, InvalidParameterValueException, ServerApiException { - CallContext.current().setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getRouterId())); + CallContext.current().setEventDetails("Router ID: " + getResourceUuid(ApiConstants.ROUTER_ID)); VirtualRouter router = _routerService.findRouter(getRouterId()); if (router == null || router.getRole() != VirtualRouter.Role.VIRTUAL_ROUTER) { throw new InvalidParameterValueException("Can't find router by routerId"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java index 36ffdcb42d00..d90208e36d34 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/RebootRouterCmd.java @@ -78,7 +78,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "rebooting router: " + this._uuidMgr.getUuid(VirtualMachine.class,getId()); + return "Rebooting router with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -97,7 +97,7 @@ public boolean isForced() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class,getId())); + CallContext.current().setEventDetails("Router ID: " + getResourceUuid(ApiConstants.ID)); VirtualRouter result = _routerService.rebootRouter(getId(), true, isForced()); if (result != null) { DomainRouterResponse response = _responseGenerator.createDomainRouterResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StartRouterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StartRouterCmd.java index 65849be0d685..263f53ca2e48 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StartRouterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StartRouterCmd.java @@ -81,7 +81,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "starting router: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Starting virtual router with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -96,7 +96,7 @@ public Long getApiResourceId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Router Id: " + getResourceUuid(ApiConstants.ID)); VirtualRouter result = null; VirtualRouter router = _routerService.findRouter(getId()); if (router == null || router.getRole() != Role.VIRTUAL_ROUTER) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StopRouterCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StopRouterCmd.java index 775ffcb4d38e..838762740281 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StopRouterCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/StopRouterCmd.java @@ -79,7 +79,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Stopping router: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Stopping virtual router with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -98,7 +98,7 @@ public boolean isForced() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException { - CallContext.current().setEventDetails("Router Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Router ID: " + getResourceUuid(ApiConstants.ID)); VirtualRouter result = null; VirtualRouter router = _routerService.findRouter(getId()); if (router == null || router.getRole() != Role.VIRTUAL_ROUTER) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/UpgradeRouterTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/UpgradeRouterTemplateCmd.java index 1bbc3a141dbc..6fdfa7d3642d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/router/UpgradeRouterTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/router/UpgradeRouterTemplateCmd.java @@ -119,7 +119,7 @@ public Long getInstanceId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Upgrading router Template"); + CallContext.current().setEventDetails("Upgrading router with with ID: " + getResourceUuid(ApiConstants.ID) + " template"); List result = _routerService.upgradeRouterTemplate(this); if (result != null) { ListResponse response = _responseGenerator.createUpgradeRouterTemplateResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddImageStoreS3CMD.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddImageStoreS3CMD.java index 2fe3c7cd106a..75fcf125eb10 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddImageStoreS3CMD.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddImageStoreS3CMD.java @@ -27,7 +27,7 @@ import static org.apache.cloudstack.api.ApiConstants.S3_HTTPS_FLAG; import static org.apache.cloudstack.api.ApiConstants.S3_MAX_ERROR_RETRY; import static org.apache.cloudstack.api.ApiConstants.S3_SIGNER; -import static org.apache.cloudstack.api.ApiConstants.S3_SECRET_KEY; +import static org.apache.cloudstack.api.ApiConstants.SECRET_KEY; import static org.apache.cloudstack.api.ApiConstants.S3_SOCKET_TIMEOUT; import static org.apache.cloudstack.api.ApiConstants.S3_USE_TCP_KEEPALIVE; import static org.apache.cloudstack.api.BaseCmd.CommandType.BOOLEAN; @@ -64,7 +64,7 @@ public final class AddImageStoreS3CMD extends BaseCmd implements ClientOptions { @Parameter(name = S3_ACCESS_KEY, type = STRING, required = true, description = "S3 access key") private String accessKey; - @Parameter(name = S3_SECRET_KEY, type = STRING, required = true, description = "S3 secret key") + @Parameter(name = SECRET_KEY, type = STRING, required = true, description = "S3 secret key") private String secretKey; @Parameter(name = S3_END_POINT, type = STRING, required = true, description = "S3 endpoint") @@ -101,7 +101,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE Map dm = new HashMap(); dm.put(ApiConstants.S3_ACCESS_KEY, getAccessKey()); - dm.put(ApiConstants.S3_SECRET_KEY, getSecretKey()); + dm.put(ApiConstants.SECRET_KEY, getSecretKey()); dm.put(ApiConstants.S3_END_POINT, getEndPoint()); dm.put(ApiConstants.S3_BUCKET_NAME, getBucketName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmd.java index b779ba2a2b47..460b8d642e90 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmd.java @@ -56,6 +56,9 @@ public class AddObjectStoragePoolCmd extends BaseCmd { @Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "the tags for the storage pool") private String tags; + @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, description = "the total size of the object store in GiB. Used for tracking capacity and sending alerts", since = "4.21") + private Long size; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,6 +71,10 @@ public String getName() { return name; } + public Long getTotalSize() { + return size; + } + public Map getDetails() { Map detailsMap = null; if (details != null && !details.isEmpty()) { @@ -112,7 +119,7 @@ public long getEntityOwnerId() { @Override public void execute(){ try{ - ObjectStore result = _storageService.discoverObjectStore(getName(), getUrl(), getProviderName(), getDetails()); + ObjectStore result = _storageService.discoverObjectStore(getName(), getUrl(), getTotalSize(), getProviderName(), getDetails()); ObjectStoreResponse storeResponse = null; if (result != null) { storeResponse = _responseGenerator.createObjectStoreResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CancelPrimaryStorageMaintenanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CancelPrimaryStorageMaintenanceCmd.java index 35f7b19f709d..7b69d25caef4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CancelPrimaryStorageMaintenanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CancelPrimaryStorageMaintenanceCmd.java @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "canceling maintenance for primary storage pool: " + getId(); + return "Canceling maintenance mode for primary storage pool with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java index d3b6a0746106..3bb16dfea5a4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ChangeStoragePoolScopeCmd.java @@ -28,7 +28,6 @@ import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; -import com.cloud.storage.StoragePool; @APICommand(name = "changeStoragePoolScope", description = "Changes the scope of a storage pool when the pool is in Disabled state." + "This feature is officially tested and supported for Hypervisors: KVM and VMware, Protocols: NFS and Ceph, and Storage Provider: DefaultPrimary. " + @@ -61,15 +60,7 @@ public String getEventType() { @Override public String getEventDescription() { - String description = "Change storage pool scope. Storage pool Id: "; - StoragePool pool = _entityMgr.findById(StoragePool.class, getId()); - if (pool != null) { - description += pool.getUuid(); - } else { - description += getId(); - } - description += " to " + getScope(); - return description; + return "Changing storage pool with ID: " + getResourceUuid(ApiConstants.ID) + " to scope " + scope; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ConfigureStorageAccessCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ConfigureStorageAccessCmd.java new file mode 100644 index 000000000000..c5459adfd2d0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ConfigureStorageAccessCmd.java @@ -0,0 +1,135 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.storage; + +import java.util.List; + +import com.cloud.event.EventTypes; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.ZoneResponse; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; + +import com.cloud.user.Account; + +@APICommand(name = "configureStorageAccess", description = "Configure the storage access groups on zone/pod/cluster/host and storage, accordingly connections to the storage pools", responseObject = SuccessResponse.class, since = "4.21.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ConfigureStorageAccessCmd extends BaseAsyncCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "UUID of the zone") + private Long zoneId; + + @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, description = "UUID of the pod") + private Long podId; + + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "UUID of the cluster") + private Long clusterId; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "UUID of the host") + private Long hostId; + + @Parameter(name = ApiConstants.STORAGE_ID, type = CommandType.UUID, entityType = StoragePoolResponse.class, description = "UUID of the Storage Pool") + private Long storageId; + + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, type = CommandType.LIST, collectionType = CommandType.STRING, + description = "comma separated list of storage access groups for connecting the storage pools and the hosts", + since = "4.21.0") + private List storageAccessGroups; + + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getZoneId() { + return zoneId; + } + + public Long getPodId() { + return podId; + } + + public Long getClusterId() { + return clusterId; + } + + public Long getHostId() { + return hostId; + } + + public Long getStorageId() { + return storageId; + } + + public List getStorageAccessGroups() { + return storageAccessGroups; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public long getEntityOwnerId() { + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.StoragePool; + } + + @Override + public void execute() { + try { + boolean result = _storageService.configureStorageAccess(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to configure storage access"); + } + } catch (Exception e) { + logger.debug("Failed to configure storage access ", e); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to configure storage access, " + e.getMessage()); + } + } + + @Override + public String getEventType() { + return EventTypes.EVENT_CONFIGURE_STORAGE_ACCESS; + } + + @Override + public String getEventDescription() { + return "Configuring storage access groups"; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java index 0ac997d56e74..2aef856f58f3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/CreateStoragePoolCmd.java @@ -61,6 +61,10 @@ public class CreateStoragePoolCmd extends BaseCmd { @Parameter(name = ApiConstants.TAGS, type = CommandType.STRING, description = "The tags for the storage pool") private String tags; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, type = CommandType.STRING, + description = "comma separated list of storage access groups for connecting to hosts having those specific groups", since = "4.21.0") + private String storageAccessGroups; + @Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "The URL of the storage pool") private String url; @@ -115,6 +119,10 @@ public String getTags() { return tags; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + public String getUrl() { return url; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java index 92019e70eca2..1d927ac5cbd6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmd.java @@ -94,6 +94,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Downloading object at path " + getPath() + " on image store " + getStoreId(); + return "Downloading object at path " + getPath() + " on image store " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStorageAccessGroupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStorageAccessGroupsCmd.java new file mode 100644 index 000000000000..d2a1757839f9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStorageAccessGroupsCmd.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.api.command.admin.storage; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.StorageAccessGroupResponse; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.response.ListResponse; + +@APICommand(name = "listStorageAccessGroups", description = "Lists storage access groups", responseObject = StorageAccessGroupResponse.class, since = "4.21.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class ListStorageAccessGroupsCmd extends BaseListCmd { + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = BaseCmd.CommandType.STRING, description = "Name of the Storage access group") + private String name; + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + public String getName() { + return name; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.StoragePool; + } + + @Override + public void execute() { + ListResponse response = _queryService.searchForStorageAccessGroups(this); + + response.setResponseName(getCommandName()); + + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java index 47bedc22d73e..236a3769b64c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/ListStoragePoolsCmd.java @@ -74,6 +74,10 @@ public class ListStoragePoolsCmd extends BaseListCmd { @Parameter(name = ApiConstants.STORAGE_CUSTOM_STATS, type = CommandType.BOOLEAN, description = "If true, lists the custom stats of the storage pool", since = "4.18.1") private Boolean customStats; + + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUP, type = CommandType.STRING, description = "the name of the storage access group", since = "4.21.0") + private String storageAccessGroup; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -134,6 +138,17 @@ public Boolean getCustomStats() { return customStats != null && customStats; } + public String getStorageAccessGroup() { + return storageAccessGroup; + } + + public ListStoragePoolsCmd() { + } + + public ListStoragePoolsCmd(String storageAccessGroup) { + this.storageAccessGroup = storageAccessGroup; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/PreparePrimaryStorageForMaintenanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/PreparePrimaryStorageForMaintenanceCmd.java index 818b3a5bbeab..9f0efe7f7a1b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/PreparePrimaryStorageForMaintenanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/PreparePrimaryStorageForMaintenanceCmd.java @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "preparing storage pool: " + getId() + " for maintenance"; + return "Preparing storage pool with ID: " + getResourceUuid(ApiConstants.ID) + " for maintenance"; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/SyncStoragePoolCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/SyncStoragePoolCmd.java index 9f81f2f6c86c..684243c08299 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/SyncStoragePoolCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/SyncStoragePoolCmd.java @@ -67,7 +67,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Attempting to synchronise storage pool with management server"; + return "Attempting to synchronise storage pool with ID:" + getResourceUuid(ApiConstants.ID) + " with management server"; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmd.java index 497179d25ef1..ac007137ef1f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/storage/UpdateObjectStoragePoolCmd.java @@ -44,6 +44,8 @@ public class UpdateObjectStoragePoolCmd extends BaseCmd { @Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "the url for the object store") private String url; + @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, description = "the total size of the object store in GiB. Used for tracking capacity and sending alerts. Set to 0 to stop tracking.", since = "4.21") + private Long size; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -61,6 +63,10 @@ public String getUrl() { return url; } + public Long getSize() { + return size; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/DestroySystemVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/DestroySystemVmCmd.java index 3a7e1caa4dc3..6b776d067784 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/DestroySystemVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/DestroySystemVmCmd.java @@ -76,7 +76,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Destroying system Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Destroying System VM with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -91,14 +91,14 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("System VM ID: " + getResourceUuid(ApiConstants.ID)); VirtualMachine instance = _mgr.destroySystemVM(this); if (instance != null) { SystemVmResponse response = _responseGenerator.createSystemVmResponse(instance); response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Fail to destroy system vm"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to destroy System VM"); } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java index 8319883ec442..feeb3f44553d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/MigrateSystemVMCmd.java @@ -120,7 +120,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Attempting to migrate Instance Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVirtualMachineId()) + " to host Id: " + this._uuidMgr.getUuid(Host.class, getHostId()); + return "Attempting to migrate System VM with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } @Override @@ -138,7 +138,7 @@ public void execute() { if (destStoragePool == null) { throw new InvalidParameterValueException("Unable to find the storage pool to migrate the Instance"); } - CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to storage pool Id: " + getStorageId()); + CallContext.current().setEventDetails("System VM ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to storage pool with ID: " + getResourceUuid(ApiConstants.STORAGE_ID)); migratedVm = _userVmService.vmStorageMigration(getVirtualMachineId(), destStoragePool); } else { Host destinationHost = null; @@ -153,7 +153,7 @@ public void execute() { } else if (! isAutoSelect()) { throw new InvalidParameterValueException("Please specify a host or storage as destination, or pass 'autoselect=true' to automatically select a destination host which do not require storage migration"); } - CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to host Id: " + getHostId()); + CallContext.current().setEventDetails("System VM ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to host with ID: " + getResourceUuid(ApiConstants.HOST_ID)); if (destinationHost == null) { migratedVm = _userVmService.migrateVirtualMachine(getVirtualMachineId(), null); } else { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java index 28560789a832..13f6167513b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/PatchSystemVMCmd.java @@ -19,7 +19,6 @@ import com.cloud.event.EventTypes; import com.cloud.user.Account; import com.cloud.utils.Pair; -import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -46,7 +45,7 @@ public class PatchSystemVMCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.FORCED, type = CommandType.BOOLEAN, description = "If true, initiates copy of scripts and restart of the agent, even if the scripts version matches." + "To be used with ID parameter only") - private Boolean force; + private Boolean forced; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -58,7 +57,7 @@ public Long getId() { } public boolean isForced() { - return force != null && force; + return forced != null && forced; } ///////////////////////////////////////////////////// @@ -72,7 +71,7 @@ public String getEventType() { @Override public String getEventDescription() { - return String.format("Attempting to live patch System VM with Id: %s ", this._uuidMgr.getUuid(VirtualMachine.class, getId())); + return String.format("Attempting to live patch System VM with ID: %s ", getResourceUuid(ApiConstants.ID)); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java index 81affc714525..0a0ffe847adc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/RebootSystemVmCmd.java @@ -86,7 +86,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "rebooting system vm: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Rebooting System VM with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -105,14 +105,14 @@ public boolean isForced() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); VirtualMachine result = _mgr.rebootSystemVM(this); if (result != null) { SystemVmResponse response = _responseGenerator.createSystemVmResponse(result); response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Fail to reboot system vm"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to reboot System Instance"); } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java index eaf927ae0f72..061a2ad2deed 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/ScaleSystemVMCmd.java @@ -98,7 +98,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("SystemVm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("System VM ID: " + getResourceUuid(ApiConstants.ID)); ServiceOffering serviceOffering = _entityMgr.findById(ServiceOffering.class, serviceOfferingId); if (serviceOffering == null) { @@ -137,6 +137,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Upgrading system vm: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()) + " to service offering: " + this._uuidMgr.getUuid(ServiceOffering.class, getServiceOfferingId()); + return "Upgrading System VM with ID: " + getResourceUuid(ApiConstants.ID) + " to service offering: " + getResourceUuid(ApiConstants.SERVICE_OFFERING_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StartSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StartSystemVMCmd.java index bfb6a240a62d..734b553c2dd5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StartSystemVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StartSystemVMCmd.java @@ -87,7 +87,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "starting system vm: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Starting System VM with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -102,7 +102,7 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); VirtualMachine instance = _mgr.startSystemVM(getId()); if (instance != null) { SystemVmResponse response = _responseGenerator.createSystemVmResponse(instance); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StopSystemVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StopSystemVmCmd.java index e86b6a281215..bdcb5b407b39 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StopSystemVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/StopSystemVmCmd.java @@ -89,7 +89,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Stopping system vm: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Stopping System VM with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -108,7 +108,7 @@ public boolean isForced() { @Override public void execute() throws ResourceUnavailableException, ConcurrentOperationException { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); VirtualMachine result = _mgr.stopSystemVM(this); if (result != null) { SystemVmResponse response = _responseGenerator.createSystemVmResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/UpgradeSystemVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/UpgradeSystemVMCmd.java index 4567c25a0d1a..a27c04374c32 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/UpgradeSystemVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/systemvm/UpgradeSystemVMCmd.java @@ -87,7 +87,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); ServiceOffering serviceOffering = _entityMgr.findById(ServiceOffering.class, serviceOfferingId); if (serviceOffering == null) { @@ -100,7 +100,7 @@ public void execute() { response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Fail to reboot system vm"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to reboot System VM"); } } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatePermissionsCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatePermissionsCmdByAdmin.java index a68958a64b1a..6115ff98f38a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatePermissionsCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/template/ListTemplatePermissionsCmdByAdmin.java @@ -1,4 +1,4 @@ -// Licensedname = "listTemplatePermissions", to the Apache Software Foundation (ASF) under one +// 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 diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java index 2ba3b321887d..50abd953e63f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/AddTrafficTypeCmd.java @@ -148,7 +148,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("TrafficType Id: " + getEntityId()); + CallContext.current().setEventDetails("Traffic type ID: " + getEntityUuid()); PhysicalNetworkTrafficType result = _networkService.getPhysicalNetworkTrafficType(getEntityId()); if (result != null) { TrafficTypeResponse response = _responseGenerator.createTrafficTypeResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/DeleteTrafficTypeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/DeleteTrafficTypeCmd.java index d8813eefa6af..3f07020e5fa2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/DeleteTrafficTypeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/DeleteTrafficTypeCmd.java @@ -71,7 +71,7 @@ public void execute() { @Override public String getEventDescription() { - return "Deleting Traffic Type: " + getId(); + return "Deleting Traffic Type with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficTypesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficTypesCmd.java index 28e2eefe52f9..dc6e0f5dd69d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficTypesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/ListTrafficTypesCmd.java @@ -26,14 +26,13 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PhysicalNetworkResponse; -import org.apache.cloudstack.api.response.ProviderResponse; import org.apache.cloudstack.api.response.TrafficTypeResponse; import com.cloud.network.PhysicalNetworkTrafficType; import com.cloud.user.Account; import com.cloud.utils.Pair; -@APICommand(name = "listTrafficTypes", description = "Lists traffic types of a given physical network.", responseObject = ProviderResponse.class, since = "3.0.0", +@APICommand(name = "listTrafficTypes", description = "Lists traffic types of a given physical network.", responseObject = TrafficTypeResponse.class, since = "3.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListTrafficTypesCmd extends BaseListCmd { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java index 0de4cfb7edda..29b06a3b5259 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/usage/UpdateTrafficTypeCmd.java @@ -118,7 +118,7 @@ public void execute() { @Override public String getEventDescription() { - return "Updating Traffic Type: " + getId(); + return "Updating Traffic Type with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmd.java index f03bb1c4ddd3..684103cf8d39 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmd.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang3.StringUtils; import com.cloud.user.Account; @@ -78,6 +79,12 @@ public class CreateUserCmd extends BaseCmd { @Parameter(name = ApiConstants.USER_ID, type = CommandType.STRING, description = "User UUID, required for adding account from external provisioning system") private String userUUID; + @Parameter(name = ApiConstants.PASSWORD_CHANGE_REQUIRED, + type = CommandType.BOOLEAN, + description = "Provide true to mandate the User to reset password on next login.", + since = "4.23.0") + private Boolean passwordChangeRequired; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -118,6 +125,10 @@ public String getUserUUID() { return userUUID; } + public Boolean isPasswordChangeRequired() { + return BooleanUtils.isTrue(passwordChangeRequired); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -147,7 +158,7 @@ public void execute() { CallContext.current().setEventDetails("UserName: " + getUserName() + ", FirstName :" + getFirstName() + ", LastName: " + getLastName()); User user = _accountService.createUser(getUserName(), getPassword(), getFirstName(), getLastName(), getEmail(), getTimezone(), getAccountName(), getDomainId(), - getUserUUID()); + getUserUUID(), isPasswordChangeRequired()); if (user != null) { UserResponse response = _responseGenerator.createUserResponse(user); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java index f954ac939c3c..01886187f9b9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserCmd.java @@ -82,7 +82,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - CallContext.current().setEventDetails("UserId: " + getId()); + CallContext.current().setEventDetails("User ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _regionService.deleteUser(this); if (!result) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete user"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserKeysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserKeysCmd.java new file mode 100644 index 000000000000..6cf55514ba36 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DeleteUserKeysCmd.java @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.user; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +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.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ApiKeyPairResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +@APICommand(name = "deleteUserKeys", description = "Deletes a keypair from a user", responseObject = SuccessResponse.class, + since = "4.23.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) +public class DeleteUserKeysCmd extends BaseAsyncCmd { + @ACL + @Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, required = true, description = "ID of the keypair to be deleted.") + private Long id; + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.User; + } + + @Override + public long getEntityOwnerId() { + ApiKeyPair keyPair = apiKeyPairService.findById(id); + if (keyPair != null) { + return keyPair.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + public Long getId() { + return id; + } + + @Override + public void execute() { + _accountService.deleteApiKey(this); + + SuccessResponse response = new SuccessResponse(getCommandName()); + this.setResponseObject(response); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DELETE_SECRET_API_KEY; + } + + @Override + public String getEventDescription() { + ApiKeyPair keyPair = apiKeyPairService.findById(id); + return String.format("Deleting API key pair with ID [%s]%s", + keyPair == null ? id : keyPair.getUuid(), + keyPair == null ? "." : String.format(" and name [%s].", keyPair.getName())); + } + + @Override + public Long getSyncObjId() { + return getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java index 974c1c7bebed..cc2bc0906a24 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/DisableUserCmd.java @@ -78,12 +78,12 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "disabling user: " + getId(); + return "Disabling User with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("UserId: " + getId()); + CallContext.current().setEventDetails("User ID: " + getResourceUuid(ApiConstants.ID)); UserAccount user = _regionService.disableUser(this); if (user != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java index 77d8d530daf2..9141a9bccf8a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/EnableUserCmd.java @@ -71,7 +71,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("UserId: " + getId()); + CallContext.current().setEventDetails("User ID: " + getResourceUuid(ApiConstants.ID)); UserAccount user = _regionService.enableUser(this); if (user != null) { 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/admin/user/GetUserKeysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java index cdd239f72b57..a651befcff7e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/GetUserKeysCmd.java @@ -26,39 +26,39 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; -import org.apache.cloudstack.api.response.RegisterResponse; +import org.apache.cloudstack.api.response.RegisterUserKeyResponse; import org.apache.cloudstack.api.response.UserResponse; import java.util.Map; @APICommand(name = "getUserKeys", - description = "This command allows the user to query the seceret and API keys for the account", - responseObject = RegisterResponse.class, - requestHasSensitiveInfo = false, - responseHasSensitiveInfo = true, - authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin}, - since = "4.10.0") - -public class GetUserKeysCmd extends BaseCmd{ - - @Parameter(name= ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user whose keys are required") + description = "Queries the last registered secret and API keys of a user.", + responseObject = RegisterUserKeyResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = true, + authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin}, + since = "4.10.0") +public class GetUserKeysCmd extends BaseCmd { + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user whose keys are required") private Long id; - - public Long getID(){ + public Long getId() { return id; - }public long getEntityOwnerId(){ - User user = _entityMgr.findById(User.class, getID()); - if(user != null){ + } + + public long getEntityOwnerId() { + User user = _entityMgr.findById(User.class, getId()); + if (user != null) { return user.getAccountId(); } - else return Account.ACCOUNT_ID_SYSTEM; + return Account.ACCOUNT_ID_SYSTEM; } - public void execute(){ + + public void execute() { Pair> keys = _accountService.getKeys(this); - RegisterResponse response = new RegisterResponse(); - if(keys != null){ + RegisterUserKeyResponse response = new RegisterUserKeyResponse(); + if (keys != null){ response.setApiKeyAccess(keys.first()); response.setApiKey(keys.second().get("apikey")); response.setSecretKey(keys.second().get("secretkey")); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUserKeyRulesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUserKeyRulesCmd.java new file mode 100644 index 000000000000..4345cae92911 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUserKeyRulesCmd.java @@ -0,0 +1,68 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.user; + + +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ApiKeyPairResponse; +import org.apache.cloudstack.api.response.BaseRolePermissionResponse; +import org.apache.cloudstack.api.response.ListResponse; + +import java.util.List; + +@APICommand(name = "listUserKeyRules", + description = "Lists the rules defined for a API key pair.", + responseObject = BaseRolePermissionResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.23.0") +public class ListUserKeyRulesCmd extends BaseCmd { + @ACL + @Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, description = "ID of the key pair.", required = true) + private Long id; + + public Long getId() { + return id; + } + + public long getEntityOwnerId() { + ApiKeyPair keyPair = apiKeyPairService.findById(getId()); + if (keyPair != null) { + return keyPair.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException { + List permissions = _accountService.listKeyRules(this); + ListResponse response = _responseGenerator.createKeypairPermissionsResponse(permissions); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUserKeysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUserKeysCmd.java new file mode 100644 index 000000000000..ded05d6381a0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUserKeysCmd.java @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.user; + + +import com.cloud.user.Account; +import com.cloud.user.UserAccount; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ApiKeyPairResponse; +import org.apache.cloudstack.api.response.UserResponse; + +@APICommand(name = "listUserKeys", + description = "Lists the API key pairs (API and secret keys) of a user.", + responseObject = ApiKeyPairResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = true, + authorized = {RoleType.User, RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin}, + since = "4.23.0") +public class ListUserKeysCmd extends BaseListCmd { + @ACL + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "ID of the user that owns the keys.") + private Long userId; + + @ACL + @Parameter(name = ApiConstants.KEYPAIR_ID, type = CommandType.UUID, entityType = ApiKeyPairResponse.class, description = "ID of the key pair.") + private Long keyPairId; + + @Parameter(name = ApiConstants.API_KEY_FILTER, type = CommandType.STRING, description = "API key of the key pair.") + private String apiKeyFilter; + + @Parameter(name = ApiConstants.SHOW_PERMISSIONS, type = CommandType.BOOLEAN, description = "Whether API Key rules should be returned. Defaults to false.") + private boolean showPermissions = false; + + @Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin}, + description = "Lists all API key pairs of users that are accessible by the calling account. Only available for administrators. Defaults to false.") + private boolean listAll = false; + + public Long getUserId() { + return userId; + } + + public Long getKeyId() { + return keyPairId; + } + + public String getApiKeyFilter() { + return apiKeyFilter; + } + + public Boolean getShowPermissions() { + return showPermissions; + } + + public boolean getListAll() { + return listAll; + } + + public long getEntityOwnerId() { + if (getKeyId() != null) { + ApiKeyPair keypair = apiKeyPairService.findById(getKeyId()); + if (keypair != null) { + return keypair.getAccountId(); + } + } else if (getUserId() != null) { + UserAccount userAccount = _accountService.getUserAccountById(getUserId()); + if (userAccount != null) { + return userAccount.getAccountId(); + } + } + return Account.ACCOUNT_ID_SYSTEM; + } + + public void execute() { + ListResponse finalResponse = _accountService.listKeys(this); + finalResponse.setObjectName("userkeys"); + finalResponse.setResponseName(getCommandName()); + setResponseObject(finalResponse); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java index 53e742046259..0fe83fd0796c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/ListUsersCmd.java @@ -16,9 +16,11 @@ // under the License. package org.apache.cloudstack.api.command.admin.user; +import com.cloud.exception.InvalidParameterValueException; import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceTag; import com.cloud.user.Account; +import com.cloud.user.User; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.ResourceIconResponse; @@ -30,6 +32,7 @@ import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UserResponse; +import org.apache.commons.lang3.EnumUtils; import java.util.List; @@ -63,6 +66,10 @@ public class ListUsersCmd extends BaseListAccountResourcesCmd implements UserCmd description = "Flag to display the resource icon for users") private Boolean showIcon; + @Parameter(name = ApiConstants.USER_SOURCE, type = CommandType.STRING, since = "4.21.0.0", + description = "List users by their authentication source. Valid values are: native, ldap, saml2 and saml2disabled.") + private String userSource; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -91,6 +98,23 @@ public Boolean getShowIcon() { return showIcon != null ? showIcon : false; } + public User.Source getUserSource() { + if (userSource == null) { + return null; + } + + User.Source source = EnumUtils.getEnumIgnoreCase(User.Source.class, userSource); + if (source == null || List.of(User.Source.OAUTH2, User.Source.UNKNOWN).contains(source)) { + throw new InvalidParameterValueException(String.format("Invalid user source: %s. Valid values are: native, ldap, saml2 and saml2disabled.", userSource)); + } + + if (source == User.Source.NATIVE) { + return User.Source.UNKNOWN; + } + + return source; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java index a160486c51c4..aab20f108f9e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/MoveUserCmd.java @@ -116,7 +116,7 @@ public void execute() { Preconditions.checkNotNull(getId(),"I have to have an user to move!"); Preconditions.checkState(ObjectUtils.anyNotNull(getAccountId(),getAccountName()),"provide either an account name or an account id!"); - CallContext.current().setEventDetails("UserId: " + getId()); + CallContext.current().setEventDetails("User ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _regionService.moveUser(this); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterUserKeysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterUserKeysCmd.java new file mode 100644 index 000000000000..28c79517f4b9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/RegisterUserKeysCmd.java @@ -0,0 +1,212 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.user; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import com.cloud.user.User; +import org.apache.cloudstack.acl.Rule; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.commons.lang3.StringUtils; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ApiKeyPairResponse; +import org.apache.cloudstack.api.response.UserResponse; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +@APICommand(name = "registerUserKeys", + responseObject = ApiKeyPairResponse.class, + description = "Registers an API key pair (API and secret keys) for a user.", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) +public class RegisterUserKeysCmd extends BaseAsyncCmd { + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = UserResponse.class, required = true, description = "ID of the user.") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "API key pair name.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "API key pair description.", length = 1024) + private String description; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Start date of the API key pair. " + + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS) + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Expiration date of the API key pair. " + + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) + private Date endDate; + + @Parameter(name = ApiConstants.RULES, type = CommandType.MAP, description = "The rules of the API key pair. If no rules are informed, " + + "defaults to allowing all account permissions. Otherwise, only the explicitly informed permissions for the key pair will be " + + "considered. Lower indexed rules take precedence over higher. Thus, in the following example: " + + "\"rules[0].rule=deleteUserKeys rules[0].permission=deny rules[1].rule=*UserKey* rules[1].permission=allow\", all rules matching " + + "the expression \"*UserKeys*\" will be allowed, except for \"deleteUserKeys\".") + private Map rules; + + public void setUserId(Long userId) { + this.id = userId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public void setRules(Map rules) { + this.rules = rules; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + public List> getRules() { + List> rulesDetails = new ArrayList<>(); + + if (rules == null) { + return rulesDetails; + } + + for (Object ruleObject : rules.values()) { + HashMap detail = (HashMap) ruleObject; + Map ruleDetails = new HashMap<>(); + + String rule = detail.get(ApiConstants.RULE); + if (StringUtils.isEmpty(rule)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Empty rule has been provided in the [rules] parameter."); + } + + String permission = detail.get(ApiConstants.PERMISSION); + if (StringUtils.isEmpty(permission)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, String.format("Rule [%s] has no permission associated with it," + + " please specify if it is either [allow] or [deny].", rule)); + } + ruleDetails.put(ApiConstants.RULE, new Rule(rule)); + ruleDetails.put(ApiConstants.PERMISSION, roleService.getRolePermission(permission)); + + String description = detail.get(ApiConstants.DESCRIPTION); + if (StringUtils.isNotEmpty(description)) { + if (description.length() > 255) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Rule description cannot be longer than 255 characters."); + } + ruleDetails.put(ApiConstants.DESCRIPTION, description); + } + + rulesDetails.add(ruleDetails); + } + return rulesDetails; + } + + @Override + public long getEntityOwnerId() { + User user = _entityMgr.findById(User.class, getUserId()); + List accessibleUsers = _queryService.searchForAccessibleUsers(); + if (user != null && accessibleUsers.stream().anyMatch(u -> u == user.getId())) { + return user.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + public Long getUserId() { + return id; + } + + @Override + public Long getApiResourceId() { + User user = _entityMgr.findById(User.class, getUserId()); + if (user != null) { + return user.getId(); + } + return null; + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.User; + } + + @Override + public void execute() { + ApiKeyPair apiKeyPair = _accountService.createApiKeyAndSecretKey(this); + ApiKeyPairResponse response = new ApiKeyPairResponse(); + if (apiKeyPair != null) { + response = _responseGenerator.createKeyPairResponse(apiKeyPair); + } + response.setObjectName("userkeys"); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_REGISTER_FOR_SECRET_API_KEY; + } + + @Override + public String getEventDescription() { + String userUuid = getResourceUuid(ApiConstants.ID); + return String.format("Registering API keypair for user [%s].", userUuid == null ? id : userUuid); + } + + @Override + public String getSyncObjType() { + return BaseAsyncCmd.user; + } + + @Override + public Long getSyncObjId() { + return getUserId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java index cc0b2e4954ce..3f5ce2415022 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmd.java @@ -29,6 +29,7 @@ import org.apache.cloudstack.api.response.UserResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.region.RegionService; +import org.apache.commons.lang.BooleanUtils; import com.cloud.user.Account; import com.cloud.user.User; @@ -38,13 +39,15 @@ requestHasSensitiveInfo = true, responseHasSensitiveInfo = true) public class UpdateUserCmd extends BaseCmd { + @Inject + private RegionService _regionService; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, description = "The API key for the user. Must be specified with userSecretKey") - private String apiKey; + @Parameter(name = ApiConstants.USER_API_KEY, type = CommandType.STRING, description = "Updates the latest API key of the user. Must be specified with usersecretkey") + private String userApiKey; @Parameter(name = ApiConstants.EMAIL, type = CommandType.STRING, description = "Email") private String email; @@ -67,8 +70,8 @@ public class UpdateUserCmd extends BaseCmd { @Parameter(name = ApiConstants.CURRENT_PASSWORD, type = CommandType.STRING, description = "Current password that was being used by the user. You must inform the current password when updating the password.", acceptedOnAdminPort = false) private String currentPassword; - @Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "The secret key for the user. Must be specified with userApiKey") - private String secretKey; + @Parameter(name = ApiConstants.USER_SECRET_KEY, type = CommandType.STRING, description = "Updates the latest secret key of the user. Must be specified with userapikey.") + private String userSecretKey; @Parameter(name = ApiConstants.API_KEY_ACCESS, type = CommandType.STRING, description = "Determines if Api key access for this user is enabled, disabled or inherits the value from its parent, the owning account", since = "4.20.1.0", authorized = {RoleType.Admin}) private String apiKeyAccess; @@ -85,15 +88,18 @@ public class UpdateUserCmd extends BaseCmd { "This parameter is only used to mandate 2FA, not to disable 2FA", since = "4.18.0.0") private Boolean mandate2FA; - @Inject - private RegionService _regionService; + @Parameter(name = ApiConstants.PASSWORD_CHANGE_REQUIRED, + type = CommandType.BOOLEAN, + description = "Provide true to mandate the User to reset password on next login.", + since = "4.23.0") + private Boolean passwordChangeRequired; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// public String getApiKey() { - return apiKey; + return userApiKey; } public String getEmail() { @@ -121,7 +127,7 @@ public String getCurrentPassword() { } public String getSecretKey() { - return secretKey; + return userSecretKey; } public String getApiKeyAccess() { @@ -156,7 +162,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("UserId: " + getId()); + CallContext.current().setEventDetails("User ID: " + getResourceUuid(ApiConstants.ID)); UserAccount user = _regionService.updateUser(this); if (user != null) { @@ -193,4 +199,8 @@ public Long getApiResourceId() { public ApiCommandResourceType getApiResourceType() { return ApiCommandResourceType.User; } + + public Boolean isPasswordChangeRequired() { + return BooleanUtils.isTrue(passwordChangeRequired); + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java index abf7da6f962d..67278991383c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vlan/CreateVlanIpRangeCmd.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.admin.vlan; +import com.cloud.configuration.ConfigurationService; +import com.cloud.network.Network; import com.cloud.utils.net.NetUtils; import com.cloud.utils.StringUtils; @@ -40,7 +42,6 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; -import java.util.Objects; @APICommand(name = "createVlanIpRange", description = "Creates a VLAN IP range.", responseObject = VlanIpRangeResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -115,8 +116,8 @@ public class CreateVlanIpRangeCmd extends BaseCmd { @Parameter(name = ApiConstants.FOR_SYSTEM_VMS, type = CommandType.BOOLEAN, description = "True if IP range is set to System VMs, false if not") private Boolean forSystemVms; - @Parameter(name = ApiConstants.FOR_NSX, type = CommandType.BOOLEAN, description = "true if the IP range is used for NSX resource", since = "4.20.0") - private boolean forNsx; + @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, description = "Provider name for which the IP range is reserved for", since = "4.21.0") + private String provider; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -158,12 +159,12 @@ public String getStartIp() { return startIp; } - public boolean isForNsx() { - return !Objects.isNull(forNsx) && forNsx; + public Network.Provider getProvider() { + return Network.Provider.getProvider(provider); } public String getVlan() { - if (StringUtils.isBlank(vlan) && !isForNsx()) { + if (StringUtils.isBlank(vlan) && !ConfigurationService.IsIpRangeForProvider(getProvider())) { vlan = "untagged"; } return vlan; @@ -237,7 +238,7 @@ public void execute() throws ResourceUnavailableException, ResourceAllocationExc logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } catch (InsufficientCapacityException ex) { - logger.info(ex); + logger.error(ex); throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java index e11d20d06466..50ff97ac676f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmd.java @@ -85,6 +85,9 @@ public class AssignVMCmd extends BaseCmd { "In case no security groups are provided the Instance is part of the default security group.") private List securityGroupIdList; + // Internal flag to allow assignment without adding a network + private boolean skipNetwork = false; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -113,6 +116,34 @@ public List getSecurityGroupIdList() { return securityGroupIdList; } + public boolean isSkipNetwork() { + return skipNetwork; + } + + ///////////////////////////////////////////////////// + /////////////////// Setters ///////////////////////// + ///////////////////////////////////////////////////// + + public void setVirtualMachineId(Long virtualMachineId) { + this.virtualMachineId = virtualMachineId; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + public void setProjectId(Long projectId) { + this.projectId = projectId; + } + + public void setSkipNetwork(boolean skipNetwork) { + this.skipNetwork = skipNetwork; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/CreateVMFromBackupCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/CreateVMFromBackupCmdByAdmin.java new file mode 100644 index 000000000000..280e4a8bab77 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/CreateVMFromBackupCmdByAdmin.java @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.vm; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.command.admin.AdminCmd; +import org.apache.cloudstack.api.command.user.vm.CreateVMFromBackupCmd; +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.api.response.PodResponse; +import org.apache.cloudstack.api.response.UserVmResponse; + +import com.cloud.vm.VirtualMachine; + +@APICommand(name = "createVMFromBackup", + description = "Creates and automatically starts a VM from a backup.", + responseObject = UserVmResponse.class, + responseView = ResponseObject.ResponseView.Full, + entityType = {VirtualMachine.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, + since = "4.21.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateVMFromBackupCmdByAdmin extends CreateVMFromBackupCmd implements AdminCmd { + + @Parameter(name = ApiConstants.POD_ID, type = CommandType.UUID, entityType = PodResponse.class, description = "destination Pod ID to deploy the VM to - parameter available for root admin only", since = "4.21") + private Long podId; + + @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "destination Cluster ID to deploy the VM to - parameter available for root admin only", since = "4.21") + private Long clusterId; + + private String instanceType; + + public Long getPodId() { + return podId; + } + + public Long getClusterId() { + return clusterId; + } + + @Override + public String getInstanceType() { + return instanceType; + } + + public CreateVMFromBackupCmdByAdmin(){} + + public CreateVMFromBackupCmdByAdmin(String hypervisor, String instanceType) { + this.displayVm = false; + this.hypervisor = hypervisor; + this.instanceType = instanceType; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java index e64c8b3f46c6..b7c3b2ba41a6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdmin.java @@ -41,6 +41,19 @@ public class DeployVMCmdByAdmin extends DeployVMCmd implements AdminCmd { @Parameter(name = ApiConstants.CLUSTER_ID, type = CommandType.UUID, entityType = ClusterResponse.class, description = "Destination Cluster ID to deploy the Instance to - parameter available for root admin only", since = "4.13") private Long clusterId; + @Parameter(name = ApiConstants.BLANK_INSTANCE, + type = CommandType.BOOLEAN, + description = "Whether to create a blank instance without storage and network", + since = "4.23.0") + private Boolean blankInstance; + + // Internal flag to allow deploying instance with a given type + private String instanceType; + + ///////////////////////////////////////////////////// + ////////////////// Getters ////////////////////////// + ///////////////////////////////////////////////////// + public Long getPodId() { return podId; } @@ -48,4 +61,37 @@ public Long getPodId() { public Long getClusterId() { return clusterId; } + + @Override + public boolean isBlankInstance() { + return Boolean.TRUE.equals(blankInstance); + } + + @Override + public String getInstanceType() { + if (!isBlankInstance()) { + return null; + } + return instanceType; + } + + ///////////////////////////////////////////////////// + ////////////////// Setters ////////////////////////// + ///////////////////////////////////////////////////// + + public void setClusterId(Long clusterId) { + this.clusterId = clusterId; + } + + public void setBlankInstance(boolean blankInstance) { + this.blankInstance = blankInstance; + } + + public void setInstanceType(String instanceType) { + this.instanceType = instanceType; + } + + public void setCustomId(String customId) { + this.customId = customId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java index 93d4b610b900..cbe6494d4004 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/DestroyVMCmdByAdmin.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.api.command.admin.vm; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.admin.AdminCmd; import org.apache.cloudstack.api.command.user.vm.DestroyVMCmd; @@ -27,4 +29,20 @@ @APICommand(name = "destroyVirtualMachine", description = "Destroys an Instance. Once destroyed, only the administrator can recover it.", responseObject = UserVmResponse.class, responseView = ResponseView.Full, entityType = {VirtualMachine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) -public class DestroyVMCmdByAdmin extends DestroyVMCmd implements AdminCmd {} +public class DestroyVMCmdByAdmin extends DestroyVMCmd implements AdminCmd { + + @Parameter( name = ApiConstants.FORCED, + type = CommandType.BOOLEAN, + description = "Force destroy the Instance", + since = "4.23.0") + Boolean forced; + + @Override + public boolean isForced() { + return Boolean.TRUE.equals(forced); + } + + public void setForced(Boolean forced) { + this.forced = forced; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ExpungeVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ExpungeVMCmd.java index 74ebab59de48..2d9c5f93383c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ExpungeVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ExpungeVMCmd.java @@ -81,7 +81,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Expunging Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Expunging Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -96,7 +96,7 @@ public Long getApiResourceId() { @Override public void execute() throws ResourceUnavailableException, ConcurrentOperationException { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); try { UserVm result = _userVmService.expungeVm(this.getId()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java index d60c627163b8..3284dbafe7ca 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportUnmanagedInstanceCmd.java @@ -201,9 +201,7 @@ public Map getNicNetworkList() { for (Map entry : (Collection>)nicNetworkList.values()) { String nic = entry.get(VmDetailConstants.NIC); String networkUuid = entry.get(VmDetailConstants.NETWORK); - if (logger.isDebugEnabled()) { - logger.debug(String.format("NIC, '%s', goes on net, '%s'", nic, networkUuid)); - } + logger.debug("Checking if NIC '{}' can be mapped on network '{}'", nic, networkUuid); if (StringUtils.isAnyEmpty(nic, networkUuid) || _entityMgr.findByUuid(Network.class, networkUuid) == null) { throw new InvalidParameterValueException(String.format("Network ID: %s for NIC ID: %s is invalid", networkUuid, nic)); } @@ -219,9 +217,7 @@ public Map getNicIpAddressList() { for (Map entry : (Collection>)nicIpAddressList.values()) { String nic = entry.get(VmDetailConstants.NIC); String ipAddress = StringUtils.defaultIfEmpty(entry.get(VmDetailConstants.IP4_ADDRESS), null); - if (logger.isDebugEnabled()) { - logger.debug(String.format("NIC, '%s', gets IP, '%s'", nic, ipAddress)); - } + logger.debug("Checking if IP address '{}' can be mapped to NIC '{}'", ipAddress, nic); if (StringUtils.isEmpty(nic)) { throw new InvalidParameterValueException(String.format("NIC ID: '%s' is invalid for IP address mapping", nic)); } @@ -244,9 +240,7 @@ public Map getDataDiskToDiskOfferingList() { for (Map entry : (Collection>)dataDiskToDiskOfferingList.values()) { String disk = entry.get(VmDetailConstants.DISK); String offeringUuid = entry.get(VmDetailConstants.DISK_OFFERING); - if (logger.isTraceEnabled()) { - logger.trace(String.format("disk, '%s', gets offering, '%s'", disk, offeringUuid)); - } + logger.trace("Checking if offering '{}' can be used on disk '{}'", offeringUuid, disk); if (StringUtils.isAnyEmpty(disk, offeringUuid) || _entityMgr.findByUuid(DiskOffering.class, offeringUuid) == null) { throw new InvalidParameterValueException(String.format("Disk offering ID: %s for disk ID: %s is invalid", offeringUuid, disk)); } @@ -277,8 +271,7 @@ public String getEventType() { @Override public String getEventDescription() { - String vmName = this.name; - return String.format("Importing unmanaged Instance: %s", vmName); + return "Importing unmanaged Instance: " + name; } public boolean isForced() { @@ -298,7 +291,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { Account account = CallContext.current().getCallingAccount(); if (account != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java index db43b53ab9ab..db7dcc3fb44f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ImportVmCmd.java @@ -30,6 +30,7 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.NetworkResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; @@ -159,6 +160,33 @@ public class ImportVmCmd extends ImportUnmanagedInstanceCmd { description = "(only for importing VMs from VMware to KVM) optional - if true, forces MS to export OVF from VMware to temporary storage, else uses KVM Host if ovftool is available, falls back to MS if not.") private Boolean forceMsToImportVmFiles; + @Parameter(name = ApiConstants.EXTRA_PARAMS, + type = CommandType.STRING, + since = "4.22", + description = "(only for importing VMs from VMware to KVM) optional - extra parameters to be passed on the virt-v2v command, if allowed by the administrator") + private String extraParams; + + @Parameter(name = ApiConstants.FORCE_CONVERT_TO_POOL, + type = CommandType.BOOLEAN, + since = "4.22", + description = "(only for importing VMs from VMware to KVM) optional - if true, forces virt-v2v conversions to write directly on the provided storage pool (avoid using temporary conversion pool).") + private Boolean forceConvertToPool; + + @Parameter(name = ApiConstants.OS_ID, + type = CommandType.UUID, + entityType = GuestOSResponse.class, + since = "4.22.1", + description = "(only for importing VMs from VMware to KVM) optional - the ID of the guest OS for the imported VM.") + private Long guestOsId; + + @Parameter(name = ApiConstants.USE_VDDK, + type = CommandType.BOOLEAN, + since = "4.22.1", + description = "(only for importing VMs from VMware to KVM) optional - if true, uses VDDK on the KVM conversion host for converting the VM. " + + "This parameter is mutually exclusive with " + ApiConstants.FORCE_MS_TO_IMPORT_VM_FILES + ".") + private Boolean useVddk; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -235,6 +263,10 @@ public Long getStoragePoolId() { return storagePoolId; } + public boolean getUseVddk() { + return BooleanUtils.toBooleanDefaultIfNull(useVddk, true); + } + public String getTmpPath() { return tmpPath; } @@ -248,6 +280,18 @@ public String getEventType() { return EventTypes.EVENT_VM_IMPORT; } + public String getExtraParams() { + return extraParams; + } + + public boolean getForceConvertToPool() { + return BooleanUtils.toBooleanDefaultIfNull(forceConvertToPool, false); + } + + public Long getGuestOsId() { + return guestOsId; + } + @Override public String getEventDescription() { String vmName = getName(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java new file mode 100644 index 000000000000..94b547ff4267 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/ListImportVMTasksCmd.java @@ -0,0 +1,116 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.vm; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ImportVMTaskResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.vm.ImportVmTasksManager; + +import javax.inject.Inject; + +@APICommand(name = "listImportVmTasks", + description = "List running import virtual machine tasks from a unmanaged hosts into CloudStack", + responseObject = ImportVMTaskResponse.class, + responseView = ResponseObject.ResponseView.Full, + requestHasSensitiveInfo = false, + authorized = {RoleType.Admin}, + since = "4.22") +public class ListImportVMTasksCmd extends BaseListCmd { + + @Inject + public ImportVmTasksManager importVmTasksManager; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + required = true, + description = "the zone ID") + private Long zoneId; + + @Parameter(name = ApiConstants.ACCOUNT_ID, + type = CommandType.UUID, + entityType = AccountResponse.class, + description = "the ID of the Account") + private Long accountId; + + @Parameter(name = ApiConstants.VCENTER, + type = CommandType.STRING, + description = "The name/ip of vCenter. Make sure it is IP address or full qualified domain name for host running vCenter server.") + private String vcenter; + + @Parameter(name = ApiConstants.CONVERT_INSTANCE_HOST_ID, + type = CommandType.UUID, + entityType = HostResponse.class, + description = "Conversion host of the importing task") + private Long convertHostId; + + @Parameter(name = ApiConstants.TASKS_FILTER, type = CommandType.STRING, description = "Filter tasks by state, valid options are: All, Running, Completed, Failed") + private String tasksFilter; + + public Long getZoneId() { + return zoneId; + } + + public Long getAccountId() { + return accountId; + } + + public String getVcenter() { + return vcenter; + } + + public Long getConvertHostId() { + return convertHostId; + } + + public String getTasksFilter() { + return tasksFilter; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + ListResponse response = importVmTasksManager.listImportVMTasks(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + return account.getId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java index 508b8b816310..467b92d415d2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVMCmd.java @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.admin.vm; +import com.cloud.hypervisor.Hypervisor; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.APICommand; @@ -123,15 +124,15 @@ public String getEventType() { @Override public String getEventDescription() { - String eventDescription; + String description = "Attempting to migrate Instance with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); + if (getHostId() != null) { - eventDescription = String.format("Attempting to migrate Instance id: %s to host Id: %s", getVirtualMachineId(), getHostId()); + description += " to host with ID: " +getResourceUuid(ApiConstants.HOST_ID); } else if (getStoragePoolId() != null) { - eventDescription = String.format("Attempting to migrate Instance id: %s to storage pool Id: %s", getVirtualMachineId(), getStoragePoolId()); - } else { - eventDescription = String.format("Attempting to migrate Instance id: %s", getVirtualMachineId()); + description = " to storage pool with ID: " + getResourceUuid(ApiConstants.STORAGE_ID); } - return eventDescription; + + return description; } @Override @@ -145,6 +146,10 @@ public void execute() { throw new InvalidParameterValueException("Unable to find the Instance by id=" + getVirtualMachineId()); } + if (Hypervisor.HypervisorType.External.equals(userVm.getHypervisorType())) { + throw new InvalidParameterValueException("Migrate VM instance operation is not allowed for External hypervisor type"); + } + Host destinationHost = null; // OfflineMigration performed when this parameter is specified StoragePool destStoragePool = null; @@ -153,7 +158,7 @@ public void execute() { if (destStoragePool == null) { throw new InvalidParameterValueException("Unable to find the storage pool to migrate the Instance"); } - CallContext.current().setEventDetails("VM Id: " + getVirtualMachineId() + " to storage pool Id: " + getStoragePoolId()); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to storage pool with ID: " + getResourceUuid(ApiConstants.STORAGE_ID)); } else if (getHostId() != null) { destinationHost = _resourceService.getHost(getHostId()); if (destinationHost == null) { @@ -162,7 +167,7 @@ public void execute() { if (destinationHost.getType() != Host.Type.Routing) { throw new InvalidParameterValueException("The specified host(" + destinationHost.getName() + ") is not suitable to migrate the Instance, please specify another one"); } - CallContext.current().setEventDetails("Instance Id: " + getVirtualMachineId() + " to host Id: " + getHostId()); + CallContext.current().setEventDetails("Instance Id: " + getVirtualMachineId() + " to host with ID: " + getResourceUuid(ApiConstants.HOST_ID)); } else if (! isAutoSelect()) { throw new InvalidParameterValueException("Please specify a host or storage as destination, or pass 'autoselect=true' to automatically select a destination host which do not require storage migration"); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java index c92cda1a358c..ad298513ac0b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmd.java @@ -135,7 +135,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Attempting to migrate Instance Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVirtualMachineId()) + " to host Id: " + this._uuidMgr.getUuid(Host.class, getHostId()); + return "Attempting to migrate Instance with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to host with ID: " + getResourceUuid(ApiConstants.HOST_ID); } @Override @@ -155,7 +155,7 @@ private Host getDestinationHost() { Host destinationHost = _resourceService.getHost(getHostId()); // OfflineVmwareMigration: destination host would have to not be a required parameter for stopped VMs if (destinationHost == null) { - logger.error(String.format("Unable to find the host with ID [%s].", getHostId())); + logger.error("Unable to find the host with ID [{}].", getHostId()); throw new InvalidParameterValueException("Unable to find the specified host to migrate the Instance."); } return destinationHost; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java index 81fbecce27ae..2c9f09dcd626 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vm/UnmanageVMInstanceCmd.java @@ -27,6 +27,7 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.user.Account; import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; @@ -36,10 +37,12 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.UnmanageVMInstanceResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.vm.UnmanagedVMsManager; +import org.apache.commons.lang3.BooleanUtils; import javax.inject.Inject; @@ -65,6 +68,20 @@ public class UnmanageVMInstanceCmd extends BaseAsyncCmd { description = "The ID of the Instance to unmanage") private Long vmId; + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, + entityType = HostResponse.class, required = false, + description = "ID of the host which will be used for unmanaging the Instance. " + + "Applicable only for KVM hypervisor and stopped Instances. Domain XML will be stored on this host.", + since = "4.22.0") + private Long hostId; + + @Parameter(name = ApiConstants.FORCED, + type = CommandType.BOOLEAN, + required = false, + description = "Force unmanaging Instance with config drive. Applicable only for KVM Hypervisor.", + since = "4.22.0") + private Boolean forced; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -80,7 +97,19 @@ public String getEventType() { @Override public String getEventDescription() { - return "Unmanaging Instance. Instance ID = " + vmId; + return "Unmanaging Instance with ID: " + getResourceUuid(ApiConstants.ID); + } + + public Long getHostId() { + return hostId; + } + + public void setHostId(Long hostId) { + this.hostId = hostId; + } + + public Boolean isForced() { + return BooleanUtils.isTrue(forced); } ///////////////////////////////////////////////////// @@ -92,10 +121,11 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { UnmanageVMInstanceResponse response = new UnmanageVMInstanceResponse(); try { - CallContext.current().setEventDetails("VM ID = " + vmId); - boolean result = unmanagedVMsManager.unmanageVMInstance(vmId); - response.setSuccess(result); - if (result) { + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); + Pair result = unmanagedVMsManager.unmanageVMInstance(vmId, hostId, isForced()); + if (result.first()) { + response.setSuccess(true); + response.setHostId(result.second()); response.setDetails("VM unmanaged successfully"); } } catch (Exception e) { @@ -124,5 +154,4 @@ public ApiCommandResourceType getApiResourceType() { public Long getApiResourceId() { return vmId; } - } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/DestroyVolumeCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/DestroyVolumeCmdByAdmin.java index 0840b4ce6f99..de90fee102de 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/DestroyVolumeCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/DestroyVolumeCmdByAdmin.java @@ -40,7 +40,7 @@ public class DestroyVolumeCmdByAdmin extends DestroyVolumeCmd implements AdminCm @Override public void execute() { CallContext.current().setEventDetails("Volume Id: " + getId()); - Volume result = _volumeService.destroyVolume(getId(), CallContext.current().getCallingAccount(), getExpunge(), false); + Volume result = _volumeService.destroyVolume(getId(), CallContext.current().getCallingAccount(), getExpunge(), false, null); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, result); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java index 57c3ee586d35..50f4b9c1fbe5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmd.java @@ -156,7 +156,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/RecoverVolumeCmdByAdmin.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/RecoverVolumeCmdByAdmin.java index e276c8a00b65..b7c084ee6e78 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/RecoverVolumeCmdByAdmin.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/RecoverVolumeCmdByAdmin.java @@ -19,6 +19,7 @@ import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ServerApiException; @@ -38,7 +39,7 @@ public class RecoverVolumeCmdByAdmin extends RecoverVolumeCmd implements AdminCm @Override public void execute() { - CallContext.current().setEventDetails("Volume Id: " + getId()); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); Volume result = _volumeService.recoverVolume(getId()); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java index dcc8b2af8d74..ac573dd4ecb9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmd.java @@ -81,7 +81,7 @@ public String getEventType() { @Override public String getEventDescription() { - return String.format("Unmanaging Volume with ID %s", volumeId); + return "Unmanaging Volume with ID: " + getResourceUuid(ApiConstants.ID); } ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CloneVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CloneVPCOfferingCmd.java new file mode 100644 index 000000000000..2148ff5c2d4f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CloneVPCOfferingCmd.java @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.vpc; + +import com.cloud.exception.ResourceAllocationException; +import com.cloud.network.vpc.VpcOffering; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.VpcOfferingResponse; + +import java.util.List; + +@APICommand(name = "cloneVPCOffering", + description = "Clones an existing VPC offering. All parameters are copied from the source offering unless explicitly overridden. " + + "Use 'addServices' and 'dropServices' to modify the service list without respecifying everything.", + responseObject = VpcOfferingResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.23.0") +public class CloneVPCOfferingCmd extends CreateVPCOfferingCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.SOURCE_OFFERING_ID, + type = BaseCmd.CommandType.UUID, + entityType = VpcOfferingResponse.class, + required = true, + description = "The ID of the source VPC offering to clone from") + private Long sourceOfferingId; + + @Parameter(name = "addservices", + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = "Services to add to the cloned offering (in addition to source offering services). " + + "If specified along with 'supportedservices', this parameter is ignored.") + private List addServices; + + @Parameter(name = "dropservices", + type = CommandType.LIST, + collectionType = CommandType.STRING, + description = "Services to remove from the cloned offering (that exist in source offering). " + + "If specified along with 'supportedservices', this parameter is ignored.") + private List dropServices; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getSourceOfferingId() { + return sourceOfferingId; + } + + public List getAddServices() { + return addServices; + } + + public List getDropServices() { + return dropServices; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void create() throws ResourceAllocationException { + // Set a temporary entity ID (source offering ID) to prevent NullPointerException + // in ApiServer.queueCommand(). This will be updated in execute() with the actual + // cloned offering ID. + if (sourceOfferingId != null) { + setEntityId(sourceOfferingId); + } + } + + @Override + public void execute() { + VpcOffering result = _vpcProvSvc.cloneVPCOffering(this); + if (result != null) { + setEntityId(result.getId()); + setEntityUuid(result.getUuid()); + + VpcOfferingResponse response = _responseGenerator.createVpcOfferingResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to clone VPC offering"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java index 76b1ee6ad77e..2b934a60da7a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCOfferingCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.admin.vpc; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -25,10 +26,11 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.Network; import com.cloud.network.VirtualRouterProvider; +import com.cloud.offering.NetworkOffering; import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.commons.collections.CollectionUtils; @@ -57,6 +59,11 @@ import static com.cloud.network.Network.Service.PortForwarding; import static com.cloud.network.Network.Service.NetworkACL; import static com.cloud.network.Network.Service.UserData; +import static com.cloud.network.Network.Service.Gateway; + +import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisNatted; +import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNetrisRouted; +import static org.apache.cloudstack.api.command.utils.OfferingUtils.isNsxWithoutLb; @APICommand(name = "createVPCOffering", description = "Creates VPC offering", responseObject = VpcOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -112,12 +119,19 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { since = "4.13") private List zoneIds; + @Deprecated @Parameter(name = ApiConstants.FOR_NSX, type = CommandType.BOOLEAN, description = "true if network offering is meant to be used for NSX, false otherwise.", since = "4.20.0") private Boolean forNsx; + @Parameter(name = ApiConstants.PROVIDER, + type = CommandType.STRING, + description = "Name of the provider providing the service", + since = "4.21.0") + private String provider; + @Parameter(name = ApiConstants.NSX_SUPPORT_LB, type = CommandType.BOOLEAN, description = "true if network offering for NSX VPC offering supports Load balancer service.", @@ -146,6 +160,12 @@ public class CreateVPCOfferingCmd extends BaseAsyncCreateCmd { description = "the routing mode for the VPC offering. Supported types are: Static or Dynamic.") private String routingMode; + @Parameter(name = ApiConstants.CONSERVE_MODE, type = CommandType.BOOLEAN, + since = "4.23.0", + description = "True if the VPC offering is IP conserve mode enabled, allowing public IPs to be used across multiple VPC tiers. Default value is false") + private Boolean conserveMode; + + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -158,29 +178,38 @@ public String getDisplayText() { return StringUtils.isEmpty(displayText) ? vpcOfferingName : displayText; } + public boolean isExternalNetworkProvider() { + return Arrays.asList("NSX", "Netris").stream() + .anyMatch(s -> provider != null && s.equalsIgnoreCase(provider)); + } + public List getSupportedServices() { - if (!isForNsx() && CollectionUtils.isEmpty(supportedServices)) { - throw new InvalidParameterValueException("Supported services needs to be provided"); - } - if (isForNsx()) { + // For external network providers, auto-populate services based on network mode + if (isExternalNetworkProvider()) { supportedServices = new ArrayList<>(List.of( Dhcp.getName(), Dns.getName(), - StaticNat.getName(), - SourceNat.getName(), NetworkACL.getName(), - PortForwarding.getName(), UserData.getName() )); - if (getNsxSupportsLbService()) { + if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) { + supportedServices.addAll(Arrays.asList( + StaticNat.getName(), + SourceNat.getName(), + PortForwarding.getName())); + } + if (NetworkOffering.NetworkMode.ROUTED.name().equalsIgnoreCase(getNetworkMode())) { + supportedServices.add(Gateway.getName()); + } + if (getNsxSupportsLbService() || isNetrisNatted(getProvider(), getNetworkMode())) { supportedServices.add(Lb.getName()); } } return supportedServices; } - public boolean isForNsx() { - return BooleanUtils.isTrue(forNsx); + public String getProvider() { + return provider; } public String getNetworkMode() { @@ -193,15 +222,13 @@ public boolean getNsxSupportsLbService() { public Map> getServiceProviders() { Map> serviceProviderMap = new HashMap<>(); - if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isForNsx()) { + if (serviceProviderList != null && !serviceProviderList.isEmpty() && !isExternalNetworkProvider()) { Collection> servicesCollection = serviceProviderList.values(); Iterator> iter = servicesCollection.iterator(); while (iter.hasNext()) { Map obj = iter.next(); - if (logger.isTraceEnabled()) { - logger.trace("service provider entry specified: " + obj); - } - HashMap services = (HashMap) obj; + logger.trace("Service provider entry specified: {}", obj); + HashMap services = (HashMap)obj; String service = services.get("service"); String provider = services.get("provider"); List providerList = null; @@ -213,16 +240,18 @@ public Map> getServiceProviders() { providerList.add(provider); serviceProviderMap.put(service, providerList); } - } else if (Boolean.TRUE.equals(forNsx)) { - getServiceProviderMapForNsx(serviceProviderMap); + } else if (isExternalNetworkProvider()) { + getServiceProviderMapForExternalProvider(serviceProviderMap, Network.Provider.getProvider(provider).getName()); } return serviceProviderMap; } - private void getServiceProviderMapForNsx(Map> serviceProviderMap) { - List unsupportedServices = List.of("Vpn", "BaremetalPxeService", "SecurityGroup", "Connectivity", - "Gateway", "Firewall"); + private void getServiceProviderMapForExternalProvider(Map> serviceProviderMap, String provider) { + List unsupportedServices = new ArrayList<>(List.of("Vpn", "BaremetalPxeService", "SecurityGroup", "Connectivity", "Firewall")); + if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode())) { + unsupportedServices.add("Gateway"); + } List routerSupported = List.of("Dhcp", "Dns", "UserData"); List allServices = Network.Service.listAllServices().stream().map(Network.Service::getName).collect(Collectors.toList()); for (String service : allServices) { @@ -230,10 +259,12 @@ private void getServiceProviderMapForNsx(Map> serviceProvid continue; if (routerSupported.contains(service)) serviceProviderMap.put(service, List.of(VirtualRouterProvider.Type.VPCVirtualRouter.name())); - else - serviceProviderMap.put(service, List.of(Network.Provider.Nsx.getName())); + else if (NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(getNetworkMode()) || + Stream.of(NetworkACL.getName(), Gateway.getName()).anyMatch(s -> s.equalsIgnoreCase(service))) { + serviceProviderMap.put(service, List.of(provider)); + } } - if (!getNsxSupportsLbService()) { + if ((isNsxWithoutLb(getProvider(), getNsxSupportsLbService())) || isNetrisRouted(getProvider(), getNetworkMode())) { serviceProviderMap.remove(Lb.getName()); } } @@ -283,6 +314,10 @@ public String getRoutingMode() { return routingMode; } + public boolean isConserveMode() { + return BooleanUtils.toBoolean(conserveMode); + } + @Override public void create() throws ResourceAllocationException { VpcOffering vpcOff = _vpcProvSvc.createVpcOffering(this); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeletePrivateGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeletePrivateGatewayCmd.java index cd88d81da675..a6b0538062b1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeletePrivateGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeletePrivateGatewayCmd.java @@ -66,7 +66,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting private gateway id=" + id); + return "Deleting private gateway with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -76,7 +76,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, ConcurrentOperationException { - CallContext.current().setEventDetails("Network ACL Id: " + id); + CallContext.current().setEventDetails("Network ACL ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _vpcService.deleteVpcPrivateGateway(id); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeleteVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeleteVPCOfferingCmd.java index b322a6d55890..f579eeb87e4d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeleteVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/DeleteVPCOfferingCmd.java @@ -76,7 +76,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting VPC offering id=" + getId(); + return "Deleting VPC offering with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java index 44bc88c8daf5..97f30f6fa2ef 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/vpc/UpdateVPCOfferingCmd.java @@ -16,7 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.admin.vpc; -import java.util.ArrayList; import java.util.List; import org.apache.cloudstack.api.APICommand; @@ -26,19 +25,16 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.offering.DomainAndZoneIdResolver; import org.apache.cloudstack.api.response.VpcOfferingResponse; -import org.apache.commons.lang3.StringUtils; -import com.cloud.dc.DataCenter; -import com.cloud.domain.Domain; import com.cloud.event.EventTypes; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.network.vpc.VpcOffering; import com.cloud.user.Account; @APICommand(name = "updateVPCOffering", description = "Updates VPC offering", responseObject = VpcOfferingResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class UpdateVPCOfferingCmd extends BaseAsyncCmd { +public class UpdateVPCOfferingCmd extends BaseAsyncCmd implements DomainAndZoneIdResolver { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -93,63 +89,11 @@ public String getState() { } public List getDomainIds() { - List validDomainIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(domainIds)) { - if (domainIds.contains(",")) { - String[] domains = domainIds.split(","); - for (String domain : domains) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domain.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid domain has been specified."); - } - } - } else { - domainIds = domainIds.trim(); - if (!domainIds.matches("public")) { - Domain validDomain = _entityMgr.findByUuid(Domain.class, domainIds.trim()); - if (validDomain != null) { - validDomainIds.add(validDomain.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid domain has been specified."); - } - } - } - } else { - validDomainIds.addAll(_vpcProvSvc.getVpcOfferingDomains(id)); - } - return validDomainIds; + return resolveDomainIds(domainIds, id, _vpcProvSvc::getVpcOfferingDomains, "VPC offering"); } public List getZoneIds() { - List validZoneIds = new ArrayList<>(); - if (StringUtils.isNotEmpty(zoneIds)) { - if (zoneIds.contains(",")) { - String[] zones = zoneIds.split(","); - for (String zone : zones) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zone.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid zone has been specified."); - } - } - } else { - zoneIds = zoneIds.trim(); - if (!zoneIds.matches("all")) { - DataCenter validZone = _entityMgr.findByUuid(DataCenter.class, zoneIds.trim()); - if (validZone != null) { - validZoneIds.add(validZone.getId()); - } else { - throw new InvalidParameterValueException("Failed to create VPC offering because invalid zone has been specified."); - } - } - } - } else { - validZoneIds.addAll(_vpcProvSvc.getVpcOfferingZones(id)); - } - return validZoneIds; + return resolveZoneIds(zoneIds, id, _vpcProvSvc::getVpcOfferingZones, "VPC offering"); } public Integer getSortKey() { @@ -183,7 +127,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating VPC offering id=" + getId(); + return "Updating VPC offering with ID:" + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java index 1066c614af86..d7f448d6ece6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/CreateZoneCmd.java @@ -31,6 +31,8 @@ import com.cloud.dc.DataCenter; import com.cloud.user.Account; +import java.util.List; + @APICommand(name = "createZone", description = "Creates a Zone.", responseObject = ZoneResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class CreateZoneCmd extends BaseCmd { @@ -88,6 +90,11 @@ public class CreateZoneCmd extends BaseCmd { @Parameter(name = ApiConstants.IS_EDGE, type = CommandType.BOOLEAN, description = "True if the zone is an edge zone, false otherwise", since = "4.18.0") private Boolean isEdge; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUPS, + type = CommandType.LIST, collectionType = CommandType.STRING, + description = "comma separated list of storage access groups for the hosts in the zone", + since = "4.21.0") + private List storageAccessGroups; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -162,6 +169,10 @@ public boolean isEdge() { return isEdge; } + public List getStorageAccessGroups() { + return storageAccessGroups; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/DeleteZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/DeleteZoneCmd.java index 565baab6e4c6..28d14a318753 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/DeleteZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/DeleteZoneCmd.java @@ -60,7 +60,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Zone Id: " + getId()); + CallContext.current().setEventDetails("Zone ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _configService.deleteZone(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/MarkDefaultZoneForAccountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/MarkDefaultZoneForAccountCmd.java index 5d3f5dcd47fa..42040290a411 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/MarkDefaultZoneForAccountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/MarkDefaultZoneForAccountCmd.java @@ -95,7 +95,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Marking account with the default zone: " + getDefaultZoneId(); + return "Marking zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID) + " as default for account " + getResourceUuid(ApiConstants.ACCOUNT) + " in domain: " + getResourceUuid(ApiConstants.DOMAIN_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java index 3e7fffb5fafc..888ee6603ddf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/admin/zone/UpdateZoneCmd.java @@ -178,7 +178,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Zone Id: " + getId()); + CallContext.current().setEventDetails("Zone ID: " + getResourceUuid(ApiConstants.ID)); DataCenter result = _configService.editZone(this); if (result != null) { ZoneResponse response = _responseGenerator.createZoneResponse(ResponseView.Full, result, false, false); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java b/api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java new file mode 100644 index 000000000000..b302c4a9beec --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolver.java @@ -0,0 +1,114 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.offering; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.LongFunction; + +import com.cloud.dc.DataCenter; +import com.cloud.domain.Domain; +import com.cloud.exception.InvalidParameterValueException; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Helper for commands that accept a domainIds or zoneIds string and need to + * resolve them to lists of IDs, falling back to an offering-specific + * default provider. + */ +public interface DomainAndZoneIdResolver { + /** + * Parse the provided domainIds string and return a list of domain IDs. + * If domainIds is empty, the defaultDomainsProvider will be invoked with the + * provided resource id to obtain the current domains. + */ + default List resolveDomainIds(final String domainIds, final Long id, final LongFunction> defaultDomainsProvider, final String resourceTypeName) { + final List validDomainIds = new ArrayList<>(); + final BaseCmd base = (BaseCmd) this; + final Logger logger = LogManager.getLogger(base.getClass()); + + if (StringUtils.isEmpty(domainIds)) { + if (defaultDomainsProvider != null) { + final List defaults = defaultDomainsProvider.apply(id); + if (defaults != null) { + validDomainIds.addAll(defaults); + } + } + return validDomainIds; + } + + final String[] domains = domainIds.split(","); + final String type = (resourceTypeName == null || resourceTypeName.isEmpty()) ? "offering" : resourceTypeName; + for (String domain : domains) { + final String trimmed = domain == null ? "" : domain.trim(); + if (trimmed.isEmpty() || "public".equalsIgnoreCase(trimmed)) { + continue; + } + + final Domain validDomain = base._entityMgr.findByUuid(Domain.class, trimmed); + if (validDomain == null) { + logger.warn("Invalid domain specified for {}", type); + throw new InvalidParameterValueException("Failed to create " + type + " because invalid domain has been specified."); + } + validDomainIds.add(validDomain.getId()); + } + + return validDomainIds; + } + + /** + * Parse the provided zoneIds string and return a list of zone IDs. + * If zoneIds is empty, the defaultZonesProvider will be invoked with the + * provided resource id to obtain the current zones. + */ + default List resolveZoneIds(final String zoneIds, final Long id, final LongFunction> defaultZonesProvider, final String resourceTypeName) { + final List validZoneIds = new ArrayList<>(); + final BaseCmd base = (BaseCmd) this; + final Logger logger = LogManager.getLogger(base.getClass()); + + if (StringUtils.isEmpty(zoneIds)) { + if (defaultZonesProvider != null) { + final List defaults = defaultZonesProvider.apply(id); + if (defaults != null) { + validZoneIds.addAll(defaults); + } + } + return validZoneIds; + } + + final String[] zones = zoneIds.split(","); + final String type = (resourceTypeName == null || resourceTypeName.isEmpty()) ? "offering" : resourceTypeName; + for (String zone : zones) { + final String trimmed = zone == null ? "" : zone.trim(); + if (trimmed.isEmpty() || "all".equalsIgnoreCase(trimmed)) { + continue; + } + + final DataCenter validZone = base._entityMgr.findByUuid(DataCenter.class, trimmed); + if (validZone == null) { + logger.warn("Invalid zone specified for {}: {}", type, trimmed); + throw new InvalidParameterValueException("Failed to create " + type + " because invalid zone has been specified."); + } + validZoneIds.add(validZone.getId()); + } + + return validZoneIds; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java index 6342709280ae..63a0a6ca51e9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddAccountToProjectCmd.java @@ -112,7 +112,7 @@ public void execute() throws ResourceAllocationException { throw new InvalidParameterValueException("Either accountName or email is required"); } - CallContext.current().setEventDetails("Project ID: " + projectId + "; accountName " + accountName); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.PROJECT_ID) + "; accountName " + accountName); boolean result = _projectService.addAccountToProject(getProjectId(), getAccountName(), getEmail(), getProjectRoleId(), getRoleType()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -147,10 +147,12 @@ public String getEventType() { @Override public String getEventDescription() { + String projectUuid = getResourceUuid(ApiConstants.PROJECT_ID); + if (accountName != null) { - return "Adding Account " + getAccountName() + " to project: " + getProjectId(); + return "Adding account " + getAccountName() + " to project: " + projectUuid; } else { - return "Sending invitation to email " + email + " to join project: " + getProjectId(); + return "Sending invitation to email " + email + " to join project: " + projectUuid; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java index 0a2d8824a5bd..683522039b17 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/AddUserToProjectCmd.java @@ -104,7 +104,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Adding User " + getUsername() + " to Project: " + getProjectId(); + return "Adding User " + getUsername() + " to Project: " + getResourceUuid(ApiConstants.PROJECT_ID); } ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java index 510f97ff7437..74e6f2c804c7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteAccountFromProjectCmd.java @@ -70,7 +70,7 @@ public String getAccountName() { @Override public void execute() { - CallContext.current().setEventDetails("Project ID: " + projectId + "; accountName " + accountName); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.PROJECT_ID) + "; accountName " + accountName); boolean result = _projectService.deleteAccountFromProject(projectId, accountName); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -103,7 +103,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing Account " + accountName + " from project: " + projectId; + return "Removing Account " + accountName + " from project: " + getResourceUuid(ApiConstants.PROJECT_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java index 5db604fe05c3..2677b206bdc1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/account/DeleteUserFromProjectCmd.java @@ -78,7 +78,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing User " + userId + " from project: " + projectId; + return "Removing User " + getResourceUuid(ApiConstants.USER_ID) + " from project: " + getResourceUuid(ApiConstants.PROJECT_ID); } @Override @@ -107,7 +107,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - CallContext.current().setEventDetails("Project ID: " + projectId + "; User ID: " + userId); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.PROJECT_ID) + "; User ID: " + getResourceUuid(ApiConstants.USER_ID)); boolean result = _projectService.deleteUserFromProject(getProjectId(), getUserId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java index c18d08299c3a..a62f9f316606 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/AssociateIPAddrCmd.java @@ -334,7 +334,7 @@ public void create() throws ResourceAllocationException { @Override public void execute() throws ResourceUnavailableException, ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { - CallContext.current().setEventDetails("IP ID: " + getEntityId()); + CallContext.current().setEventDetails("IP address ID: " + getEntityUuid()); IpAddress result = null; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java index 531f9bb86495..835a2a69e3c9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/DisassociateIPAddrCmd.java @@ -82,7 +82,7 @@ public Long getIpAddressId() { @Override public void execute() throws InsufficientAddressCapacityException { Long ipAddressId = getIpAddressId(); - CallContext.current().setEventDetails("IP ID: " + ipAddressId); + CallContext.current().setEventDetails("IP address ID: " + getResourceUuid(ApiConstants.ID)); boolean result = false; if (!isPortable()) { result = _networkService.releaseIpAddress(ipAddressId); @@ -108,7 +108,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Disassociating IP address with ID=" + id); + return ("Disassociating IP address with ID:" + getResourceUuid(ApiConstants.ID)); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java index b9730507621e..d79381e57c4e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java @@ -53,7 +53,7 @@ public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountC @Parameter(name = ApiConstants.ALLOCATED_ONLY, type = CommandType.BOOLEAN, description = "Limits search results to allocated public IP addresses") private Boolean allocatedOnly; - @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "Lists all public IP addresses by state") + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "Lists all public IP addresses by state. A comma-separated list of states can be passed.") private String state; @Parameter(name = ApiConstants.FOR_VIRTUAL_NETWORK, type = CommandType.BOOLEAN, description = "The virtual Network for the IP address") @@ -108,6 +108,9 @@ public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountC @Parameter(name = ApiConstants.FOR_SYSTEM_VMS, type = CommandType.BOOLEAN, description = "true if range is dedicated for system VMs", since = "4.20.0") private Boolean forSystemVMs; + @Parameter(name = ApiConstants.FOR_PROVIDER, type = CommandType.BOOLEAN, description = "true if range is dedicated for external network provider", since = "4.21.0") + private Boolean forProvider; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -183,6 +186,10 @@ public boolean getForSystemVMs() { return BooleanUtils.isTrue(forSystemVMs); } + public boolean isForProvider() { + return BooleanUtils.isTrue(forProvider); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ReleaseIPAddrCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ReleaseIPAddrCmd.java index 0d2ff89930d5..2fe94b29346d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/address/ReleaseIPAddrCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/address/ReleaseIPAddrCmd.java @@ -73,7 +73,7 @@ public long getEntityOwnerId() { @Override public void execute() throws InsufficientAddressCapacityException { - CallContext.current().setEventDetails("IP ID: " + getIpAddressId()); + CallContext.current().setEventDetails("IP address ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _networkService.releaseReservedIpAddress(getIpAddressId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java index 6eb75c7c1838..4eace28f5555 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/DeleteAffinityGroupCmd.java @@ -123,7 +123,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting Affinity Group"; + return "Deleting Affinity Group with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java index e522747780f2..7a7e3ee298a9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/affinitygroup/UpdateVMAffinityGroupCmd.java @@ -141,7 +141,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE throw new InvalidParameterValueException("affinitygroupids parameter or affinitygroupnames parameter must be given"); } - CallContext.current().setEventDetails("VM ID: " + getId()); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result = _affinityGroupService.updateVMAffinityGroups(getId(), getAffinityGroupIdList()); ArrayList dc = new ArrayList(); dc.add(VMDetails.valueOf("affgrp")); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScalePolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScalePolicyCmd.java index e160a1a90103..f4bfcf4f135d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScalePolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScalePolicyCmd.java @@ -160,7 +160,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "creating AutoScale Policy"; + return "Creating AutoScale Policy"; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmGroupCmd.java index 66a1a56fbe2c..7c04a4c9d53b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmGroupCmd.java @@ -185,7 +185,7 @@ public String getCreateEventDescription() { @Override public String getEventDescription() { - return "Configuring AutoScale Instance Group. Instance Group Id: " + getEntityId(); + return "Configuring AutoScale Instance Group with ID: " + getEntityId(); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java index 4ecce59fa454..db9688aa09aa 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateAutoScaleVmProfileCmd.java @@ -232,7 +232,7 @@ public static String getResultObjectName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java index 9d42706cb7d7..61745ccda7d1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/CreateConditionCmd.java @@ -127,7 +127,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public String getEventDescription() { - return "creating a condition"; + return "Creating AutoScale condition"; } @Override @@ -137,7 +137,7 @@ public String getEventType() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScalePolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScalePolicyCmd.java index 98e2e2a771ac..45315cc744ca 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScalePolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScalePolicyCmd.java @@ -79,19 +79,19 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting AutoScale Policy: " + getId(); + return "Deleting AutoScale Policy with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("AutoScale Policy Id: " + getId()); + CallContext.current().setEventDetails("AutoScale Policy ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _autoScaleService.deleteAutoScalePolicy(id); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); } else { - logger.warn("Failed to delete autoscale policy " + getId()); + logger.warn("Failed to delete autoscale policy {}", getId()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete AutoScale Policy"); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmGroupCmd.java index 274aac90262d..99d6d903ba45 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmGroupCmd.java @@ -89,19 +89,19 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting autoscale Instance group: " + getId(); + return "Deleting AutoScale Instance group with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("AutoScale Instance Group Id: " + getId()); + CallContext.current().setEventDetails("AutoScale Instance Group ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _autoScaleService.deleteAutoScaleVmGroup(id, getCleanup()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); } else { - logger.warn("Failed to delete autoscale Instance group " + getId()); + logger.warn("Failed to delete autoscale Instance group {}", getId()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete autoscale vm group"); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmProfileCmd.java index a4fbd709610a..bf1e8ecb1677 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmProfileCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteAutoScaleVmProfileCmd.java @@ -79,18 +79,18 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting autoscale Instance profile: " + getId(); + return "Deleting AutoScale Instance profile with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("AutoScale Instance Profile Id: " + getId()); + CallContext.current().setEventDetails("AutoScale Instance Profile ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _autoScaleService.deleteAutoScaleVmProfile(id); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); } else { - logger.warn("Failed to delete autoscale Instance profile " + getId()); + logger.warn("Failed to delete autoscale Instance profile {}", getId()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete autoscale Instance profile"); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteConditionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteConditionCmd.java index 7b73a76fbbf4..38b77c1553f5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteConditionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DeleteConditionCmd.java @@ -64,7 +64,7 @@ public void execute() { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); } else { - logger.warn("Failed to delete condition " + getId()); + logger.warn("Failed to delete condition {}", getId()); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete condition."); } } @@ -100,6 +100,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting a condition."; + return "Deleting AutoScale condition with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DisableAutoScaleVmGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DisableAutoScaleVmGroupCmd.java index 814f35c9f70a..316fefd62deb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DisableAutoScaleVmGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/DisableAutoScaleVmGroupCmd.java @@ -96,7 +96,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Disabling AutoScale Instance Group. Instance Group Id: " + getId(); + return "Disabling AutoScale Instance Group with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/EnableAutoScaleVmGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/EnableAutoScaleVmGroupCmd.java index 962c5af0e2c1..8aea4690425e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/EnableAutoScaleVmGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/EnableAutoScaleVmGroupCmd.java @@ -96,7 +96,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Enabling AutoScale Instance Group. Instance Group Id: " + getId(); + return "Enabling AutoScale Instance Group with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScalePolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScalePolicyCmd.java index 368f9c01fe18..05af6a53a5d3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScalePolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScalePolicyCmd.java @@ -77,7 +77,7 @@ public class UpdateAutoScalePolicyCmd extends BaseAsyncCmd { @Override public void execute() { - CallContext.current().setEventDetails("AutoScale Policy Id: " + getId()); + CallContext.current().setEventDetails("AutoScale Policy ID: " + getResourceUuid(ApiConstants.ID)); AutoScalePolicy result = _autoScaleService.updateAutoScalePolicy(this); if (result != null) { AutoScalePolicyResponse response = _responseGenerator.createAutoScalePolicyResponse(result); @@ -130,7 +130,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating Auto Scale Policy. Policy Id: " + getId(); + return "Updating AutoScale Policy with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmGroupCmd.java index 128a1368c529..3e13ce10bfb4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmGroupCmd.java @@ -97,7 +97,7 @@ public class UpdateAutoScaleVmGroupCmd extends BaseAsyncCustomIdCmd { @Override public void execute() { - CallContext.current().setEventDetails("AutoScale Instance Group Id: " + getId()); + CallContext.current().setEventDetails("AutoScale Instance Group ID: " + getResourceUuid(ApiConstants.ID)); AutoScaleVmGroup result = _autoScaleService.updateAutoScaleVmGroup(this); if (result != null) { AutoScaleVmGroupResponse response = _responseGenerator.createAutoScaleVmGroupResponse(result); @@ -151,7 +151,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating AutoScale Instance Group. Instance Group Id: " + getId(); + return "Updating AutoScale Instance Group with ID: " + getId(); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java index 5192f0382db6..9495989df189 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateAutoScaleVmProfileCmd.java @@ -124,7 +124,7 @@ public class UpdateAutoScaleVmProfileCmd extends BaseAsyncCustomIdCmd { @Override public void execute() { - CallContext.current().setEventDetails("AutoScale Policy Id: " + getId()); + CallContext.current().setEventDetails("AutoScale Policy ID: " + getResourceUuid(ApiConstants.ID)); AutoScaleVmProfile result = _autoScaleService.updateAutoScaleVmProfile(this); if (result != null) { AutoScaleVmProfileResponse response = _responseGenerator.createAutoScaleVmProfileResponse(result); @@ -190,7 +190,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating AutoScale Instance Profile. Instance Profile Id: " + getId(); + return "Updating AutoScale Instance Profile with ID: " + getId(); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java index a386db478438..43de212da7b9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/autoscale/UpdateConditionCmd.java @@ -110,6 +110,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating a condition."; + return "Updating Instance AutoScale condition with ID: " + getResourceUuid(ApiConstants.ID); } } 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 e8914e45c429..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; @@ -109,6 +120,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Assigning Instance to backup offering ID: " + offeringId; + return "Assigning Instance with ID " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to backup offering with ID: " + getResourceUuid(ApiConstants.BACKUP_OFFERING_ID); } } 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 4f852094ed7a..5b7bd518df0e 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 @@ -60,6 +60,33 @@ public class CreateBackupCmd extends BaseAsyncCreateCmd { description = "ID of the Instance") private Long vmId; + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "the name of the backup", + since = "4.21.0") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "the description for the backup", + since = "4.21.0") + private String description; + + @Parameter(name = ApiConstants.QUIESCE_VM, + type = CommandType.BOOLEAN, + required = false, + description = "Quiesce the instance before checkpointing the disks for backup. Applicable only to NAS and KBOSS backup providers. " + + "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") + private Boolean quiesceVM; + + @Parameter(name = ApiConstants.ISOLATED, + type = CommandType.BOOLEAN, + description = ApiConstants.PARAMETER_DESCRIPTION_ISOLATED_BACKUPS, + since = "4.23.0") + private boolean isolated; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -68,6 +95,22 @@ public Long getVmId() { return vmId; } + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Boolean getQuiesceVM() { + return quiesceVM; + } + + public boolean isIsolated() { + return isolated; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -75,7 +118,7 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.createBackup(getVmId()); + boolean result = backupManager.createBackup(this, getJob()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -90,7 +133,12 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE @Override public ApiCommandResourceType getApiResourceType() { - return ApiCommandResourceType.Backup; + return ApiCommandResourceType.VirtualMachine; + } + + @Override + public Long getApiResourceId() { + return vmId; } @Override @@ -105,7 +153,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating backup for Instance " + vmId; + return "Creating backup for Instance " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java new file mode 100644 index 000000000000..c5d29b615439 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/CreateBackupOfferingCmd.java @@ -0,0 +1,185 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.backup; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupOffering; + + +import javax.inject.Inject; +import java.util.List; + +@APICommand(name = "createBackupOffering", description = "Creates a backup offering", responseObject = BackupOfferingResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}, since = "4.23.0") +public class CreateBackupOfferingCmd extends BaseCmd { + + @Inject + protected BackupManager backupManager; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Backup offering name.", required = true) + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = true, + description = "The description of the backup offering") + private String description; + + @Parameter(name = ApiConstants.COMPRESS, type = CommandType.BOOLEAN, description = "Whether the backups should be compressed or not.") + private Boolean compress; + + @Parameter(name = ApiConstants.VALIDATE, type = CommandType.BOOLEAN, description = "Whether the backups should be validated or not.") + private Boolean validate; + + @Parameter(name = ApiConstants.VALIDATION_STEPS, type = CommandType.STRING, description = "Which validation steps should be performed. Accepts a comma-separated list of " + + "steps. Accepted values are: wait_for_boot, screenshot and execute_command.") + private String validationSteps; + + @Parameter(name = ApiConstants.ALLOW_QUICK_RESTORE, type = CommandType.BOOLEAN, description = "Whether quick restore is enabled for the backups or not.") + private Boolean allowQuickRestore; + + @Parameter(name = ApiConstants.ALLOW_EXTRACT_FILE, type = CommandType.BOOLEAN, description = "Whether files may be extracted from backups or not.") + private Boolean allowExtractFile; + + @Parameter(name = ApiConstants.BACKUP_CHAIN_SIZE, type = CommandType.INTEGER, description = "Backup chain size for backups created with this offering.") + private Integer backupChainSize; + + @Parameter(name = ApiConstants.COMPRESSION_LIBRARY, type = CommandType.STRING, description = "Compression library, for offerings that support compression. Accepted values " + + "are zstd and zlib. By default, zstd is used for images that support it. If the image only supports zlib, it will be used regardless of this parameter.") + private String compressionLibrary; + + @Parameter(name = ApiConstants.ZONE_ID, type = BaseCmd.CommandType.UUID, entityType = ZoneResponse.class, + description = "Restrict the backup offering to the Zone identified by this ID.", required = true) + private Long zoneId; + + @Parameter(name = ApiConstants.ALLOW_USER_DRIVEN_BACKUPS, type = CommandType.BOOLEAN, + description = "Whether users are allowed to create ad-hoc backups and backup schedules when using this offering.", required = true) + private Boolean userDrivenBackups; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = DomainResponse.class, + description = "Restrict the backup offering to the Domains identified by these IDs.") + private List domainIds; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public boolean isCompress() { + return Boolean.TRUE.equals(compress); + } + + public boolean isValidate() { + return Boolean.TRUE.equals(validate); + } + + public boolean isAllowQuickRestore() { + return Boolean.TRUE.equals(allowQuickRestore); + } + + public boolean isAllowExtractFile() { + return Boolean.TRUE.equals(allowExtractFile); + } + + public Integer getBackupChainSize() { + return backupChainSize; + } + + public Backup.CompressionLibrary getCompressionLibrary() { + if (compressionLibrary == null) { + return null; + } + try { + return Backup.CompressionLibrary.valueOf(compressionLibrary); + } catch (IllegalArgumentException e) { + throw new InvalidParameterValueException(String.format("Invalid compression library, accepted values are zstd and zlib, received [%s].", compressionLibrary)); + } + } + + public String getValidationSteps() { + if (validationSteps == null) { + return Backup.ValidationSteps.screenshot.name(); + } + StringBuilder sb = new StringBuilder(); + for (String step : validationSteps.strip().split(",")) { + try { + Backup.ValidationSteps enumStep = Backup.ValidationSteps.valueOf(step); + sb.append(enumStep.name()); + sb.append(","); + } catch (IllegalArgumentException ex) { + logger.error("Invalid validation step informed [{}].", step, ex); + throw new InvalidParameterValueException(String.format("Invalid validation step [%s] informed. Accepted values are: wait_for_boot, screenshot and execute_command.", step)); + } + } + sb.deleteCharAt(sb.lastIndexOf(",")); + return sb.toString(); + } + + public String getDescription() { + return description; + } + + public Long getZoneId() { + return zoneId; + } + + public List getDomainIds() { + return domainIds; + } + + public Boolean getUserDrivenBackups() { + return userDrivenBackups; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, + NetworkRuleConflictException { + BackupOffering offering = backupManager.createBackupOffering(this); + BackupOfferingResponse response = _responseGenerator.createBackupOfferingResponse(offering); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return 0; + } +} 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 05d903a611c3..812d78425e89 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; @@ -74,6 +75,25 @@ public class CreateBackupScheduleCmd extends BaseCmd { description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.") private String timezone; + @Parameter(name = ApiConstants.MAX_BACKUPS, type = CommandType.INTEGER, + since = "4.21.0", description = ApiConstants.PARAMETER_DESCRIPTION_MAX_BACKUPS) + private Integer maxBackups; + + @Parameter(name = ApiConstants.QUIESCE_VM, + type = CommandType.BOOLEAN, + required = false, + description = "Quiesce the Instance before checkpointing the disks for backup. Applicable only to NAS and KBOSS backup providers. " + + "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") + private Boolean quiesceVM; + + @Parameter(name = ApiConstants.ISOLATED, + type = CommandType.BOOLEAN, + description = ApiConstants.PARAMETER_DESCRIPTION_ISOLATED_BACKUPS, + since = "4.23.0") + private boolean isolated; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -94,6 +114,18 @@ public String getTimezone() { return timezone; } + public Integer getMaxBackups() { + return maxBackups; + } + + public Boolean getQuiesceVM() { + return quiesceVM; + } + + public boolean isIsolated() { + return isolated; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -118,4 +150,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/DeleteBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java index 369934a79c49..faaf1735e1e9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupCmd.java @@ -111,6 +111,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting backup ID " + backupId; + return "Deleting Backup with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java index 8f9134a5cfff..8dcf7574aed6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DeleteBackupScheduleCmd.java @@ -43,7 +43,7 @@ description = "Deletes the backup schedule of a Instance", responseObject = SuccessResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class DeleteBackupScheduleCmd extends BaseCmd { +public class DeleteBackupScheduleCmd extends BaseCmd { @Inject private BackupManager backupManager; @@ -52,17 +52,13 @@ public class DeleteBackupScheduleCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, - type = CommandType.UUID, - entityType = UserVmResponse.class, - description = "ID of the VM") + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, + description = "ID of the VM from which all backup schedules will be deleted.") private Long vmId; - @Parameter(name = ApiConstants.ID, - type = CommandType.UUID, - entityType = BackupScheduleResponse.class, - description = "ID of the schedule", - since = "4.20.1") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = BackupScheduleResponse.class, + since = "4.20.1", description = "ID of the backup schedule to be deleted. It has precedence over the 'virtualmachineid' parameter, " + + "i.e., when the 'id' parameter is specified, the 'virtualmachineid' parameter will be ignored.") private Long id; ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DownloadValidationScreenshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DownloadValidationScreenshotCmd.java new file mode 100644 index 000000000000..997d0d24c6f0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/DownloadValidationScreenshotCmd.java @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.backup; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ExtractResponse; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.InternalBackupService; + +import javax.inject.Inject; + +@APICommand(name = "downloadValidationScreenshot", description = "Download validation screenshot of given backup.", + responseObject = ExtractResponse.class, since = "4.23.0", requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class DownloadValidationScreenshotCmd extends BaseAsyncCmd { + + @Inject + private InternalBackupService internalBackupService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL + @Parameter(name = ApiConstants.BACKUP_ID, type = CommandType.UUID, entityType = BackupResponse.class, required = true, + description = "ID of the backup.") + private Long backupId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getEventType() { + return EventTypes.EVENT_SCREENSHOT_DOWNLOAD; + } + + @Override + public String getEventDescription() { + Backup backup = _entityMgr.findById(Backup.class, getBackupId()); + if (backup == null) { + throw new InvalidParameterValueException(String.format("Unable to find backup with ID [%s].", getBackupId())); + } + return "Downloading validation screenshot of backup " + backup.getUuid(); + } + + @Override + public void execute() { + ExtractResponse response = internalBackupService.downloadScreenshot(getBackupId()); + response.setResponseName(getCommandName()); + response.setObjectName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + Backup backup = _entityMgr.findById(Backup.class, getBackupId()); + if (backup != null) { + return backup.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/FinishBackupChainCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/FinishBackupChainCmd.java new file mode 100644 index 000000000000..575df7ae0831 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/FinishBackupChainCmd.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.backup; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.VirtualMachineResponse; +import org.apache.cloudstack.backup.InternalBackupService; + +import javax.inject.Inject; + +@APICommand(name = "finishBackupChain", description = "Finish the backup chain of VM. Currently only has effect on VMs with KBOSS backup offerings.", + responseObject = SuccessResponse.class, since = "4.23.0", requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class FinishBackupChainCmd extends BaseCmd { + @Inject + private InternalBackupService internalBackupService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = VirtualMachineResponse.class, required = true, + description = "ID of the VM to finish the chain.") + private Long vmId; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, + NetworkRuleConflictException { + boolean result = internalBackupService.finishBackupChain(getVmId()); + SuccessResponse response = new SuccessResponse(); + response.setSuccess(result); + response.setResponseName(getCommandName()); + response.setObjectName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, getVmId()); + if (vm != null) { + return vm.getAccountId(); + } + + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java index 1985bd6980fc..17bd06bfdd4b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmd.java @@ -24,7 +24,7 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.BackupScheduleResponse; @@ -39,7 +39,6 @@ import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.utils.exception.CloudRuntimeException; import java.util.ArrayList; import java.util.List; @@ -48,10 +47,10 @@ description = "List backup schedule of an Instance", responseObject = BackupScheduleResponse.class, since = "4.14.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class ListBackupScheduleCmd extends BaseCmd { +public class ListBackupScheduleCmd extends BaseListProjectAndAccountResourcesCmd { @Inject - private BackupManager backupManager; + BackupManager backupManager; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -60,10 +59,16 @@ public class ListBackupScheduleCmd extends BaseCmd { @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, - required = true, description = "ID of the Instance") private Long vmId; + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = BackupScheduleResponse.class, + description = "the ID of the backup schedule", + since = "4.22.0") + private Long id; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -72,6 +77,10 @@ public Long getVmId() { return vmId; } + public Long getId() { + return id; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -79,19 +88,18 @@ public Long getVmId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try{ - List schedules = backupManager.listBackupSchedule(getVmId()); + List schedules = backupManager.listBackupSchedules(this); ListResponse response = new ListResponse<>(); List scheduleResponses = new ArrayList<>(); + if (!CollectionUtils.isNullOrEmpty(schedules)) { for (BackupSchedule schedule : schedules) { scheduleResponses.add(_responseGenerator.createBackupScheduleResponse(schedule)); } - response.setResponses(scheduleResponses, schedules.size()); - response.setResponseName(getCommandName()); - setResponseObject(response); - } else { - throw new CloudRuntimeException("No backup schedule exists for the Instance"); } + response.setResponses(scheduleResponses, schedules.size()); + response.setResponseName(getCommandName()); + setResponseObject(response); } catch (Exception e) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupServiceJobsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupServiceJobsCmd.java new file mode 100644 index 000000000000..39ca444ae2ee --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupServiceJobsCmd.java @@ -0,0 +1,105 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.backup; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupServiceJobResponse; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; + +@APICommand(name = "listBackupServiceJobs", description = "List backup service jobs", responseObject = BackupServiceJobResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}, since = "4.23.0") +public class ListBackupServiceJobsCmd extends BaseListCmd { + + @Parameter(name = ApiConstants.ID, type = CommandType.LONG, entityType = BackupServiceJobResponse.class, description = "List only job with given ID.") + private Long id; + + @Parameter(name = ApiConstants.BACKUP_ID, type = CommandType.UUID, entityType = BackupResponse.class, description = "List jobs for the given backup.") + private Long backupId; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "List jobs in the given host. When passing this parameter, only jobs that are currently executing will be returned.") + private Long hostId; + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "List jobs in the given zone.") + private Long zoneId; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, description = "List jobs with the given type. Accepted values are StartCompression, FinalizeCompression and " + + "BackupValidation.") + private String type; + + @Parameter(name = ApiConstants.EXECUTING, type = CommandType.BOOLEAN, description = "List executing jobs.") + private Boolean executing; + + @Parameter(name = ApiConstants.SCHEDULED, type = CommandType.BOOLEAN, description = "List scheduled jobs.") + private Boolean scheduled; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public Long getBackupId() { + return backupId; + } + + public Long getHostId() { + return hostId; + } + + public Long getZoneId() { + return zoneId; + } + + public String getType() { + return type; + } + + public boolean getExecuting() { + return Boolean.TRUE.equals(executing); + } + + public boolean getScheduled() { + return Boolean.TRUE.equals(scheduled); + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, + NetworkRuleConflictException { + ListResponse response = _queryService.listBackupServiceJobs(this); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java index 9f34dbda14a7..35cadd9f2e5e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/ListBackupsCmd.java @@ -29,6 +29,7 @@ import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.UserVmResponse; @@ -75,6 +76,31 @@ public class ListBackupsCmd extends BaseListProjectAndAccountResourcesCmd { description = "List backups by zone id") private Long zoneId; + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + since = "4.21.0", + description = "list backups by name") + private String name; + + @Parameter(name = ApiConstants.BACKUP_OFFERING_ID, + type = CommandType.UUID, + entityType = BackupOfferingResponse.class, + since = "4.21.0", + description = "list backups by backup offering") + private Long backupOfferingId; + + @Parameter(name = ApiConstants.STATUS, + type = CommandType.STRING, + since = "4.23.0", + description = "list backups by status") + private String backupStatus; + + @Parameter(name = ApiConstants.LIST_VM_DETAILS, + type = CommandType.BOOLEAN, + since = "4.21.0", + description = "list backups with VM details") + private Boolean listVmDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -87,10 +113,26 @@ public Long getVmId() { return vmId; } + public String getName() { + return name; + } + + public Long getBackupOfferingId() { + return backupOfferingId; + } + public Long getZoneId() { return zoneId; } + public String getBackupStatus() { + return backupStatus; + } + + public Boolean getListVmDetails() { + return listVmDetails; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -101,7 +143,7 @@ protected void setupResponseBackupList(final List backups, final Integer if (backup == null) { continue; } - BackupResponse backupResponse = _responseGenerator.createBackupResponse(backup); + BackupResponse backupResponse = backupManager.createBackupResponse(backup, this.getListVmDetails()); responses.add(backupResponse); } final ListResponse response = new ListResponse<>(); @@ -116,7 +158,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE Pair, Integer> result = backupManager.listBackups(this); setupResponseBackupList(result.first(), result.second()); } catch (Exception e) { - logger.debug("Exception while listing backups", e); + logger.error("Exception while listing backups", e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); } } 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 81f11edb7d90..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; @@ -106,6 +117,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing Instance ID" + vmId + " from backup offering"; + return "Removing Instance with ID:" + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " from backup offering"; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java index 5385c0fb0b6a..9c67aa6a9f69 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/RestoreBackupCmd.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.BaseAsyncCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.BackupManager; @@ -38,6 +39,7 @@ import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.commons.lang3.BooleanUtils; @APICommand(name = "restoreBackup", description = "Restores an existing stopped or deleted Instance using an Instance backup", @@ -59,6 +61,14 @@ public class RestoreBackupCmd extends BaseAsyncCmd { description = "ID of the backup") private Long backupId; + @Parameter(name = ApiConstants.QUICK_RESTORE, type = CommandType.BOOLEAN, entityType = BackupResponse.class, description = "Whether to use the quick restore process or not. " + + "Currently this parameter is only supported by the KBOSS provider.", since = "4.23.0") + private Boolean quickRestore; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "If quickrestore is true, which host to start the VM on;" + + " otherwise, ignored. Currently this parameter is only supported by the KBOSS provider.", since = "4.23.0", authorized = {RoleType.Admin}) + private Long hostId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -67,6 +77,14 @@ public Long getBackupId() { return backupId; } + public boolean isQuickRestore() { + return BooleanUtils.isTrue(quickRestore); + } + + public Long getHostId() { + return hostId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -74,7 +92,7 @@ public Long getBackupId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackup(backupId); + boolean result = backupManager.restoreBackup(backupId, isQuickRestore(), getHostId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -99,6 +117,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Restoring Instance from backup: " + backupId; + return "Restoring Instance from backup with ID: " + getResourceUuid(ApiConstants.ID); } } 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 cee367a149c2..e05845866c2b 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 @@ -20,12 +20,15 @@ import javax.inject.Inject; 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; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.HostResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.BackupResponse; @@ -53,6 +56,7 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// + @ACL @Parameter(name = ApiConstants.BACKUP_ID, type = CommandType.UUID, entityType = BackupResponse.class, @@ -60,12 +64,14 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { description = "ID of the Instance backup") private Long backupId; + @ACL @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.STRING, required = true, description = "ID of the volume backed up") private String volumeUuid; + @ACL @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, @@ -73,6 +79,14 @@ public class RestoreVolumeFromBackupAndAttachToVMCmd extends BaseAsyncCmd { description = "ID of the Instance where to attach the restored volume") private Long vmId; + @Parameter(name = ApiConstants.QUICK_RESTORE, type = CommandType.BOOLEAN, description = "Whether to use the quick restore process or not. " + + "Currently this parameter is only supported by the KBOSS provider.", since = "4.23.0") + private Boolean quickRestore; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "If quickrestore is true, which host to start the VM on;" + + " otherwise, ignored. Currently this parameter is only supported by the KBOSS provider.", since = "4.23.0", authorized = {RoleType.Admin}) + private Long hostId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -89,6 +103,14 @@ public Long getBackupId() { return backupId; } + public boolean isQuickRestore() { + return org.apache.commons.lang3.BooleanUtils.isTrue(quickRestore); + } + + public Long getHostId() { + return hostId; + } + @Override public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); @@ -101,7 +123,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { try { - boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, backupId, vmId); + boolean result = backupManager.restoreBackupVolumeAndAttachToVM(volumeUuid, backupId, vmId, isQuickRestore(), getHostId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); @@ -121,6 +143,16 @@ public String getEventType() { @Override public String getEventDescription() { - return "Restoring volume "+ volumeUuid + " from backup " + backupId + " and attaching it to Instance " + vmId; + 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/backup/repository/AddBackupRepositoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java index 5d0c838bc377..7caa4ce710ff 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/AddBackupRepositoryCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.user.backup.repository; +import com.cloud.utils.StringUtils; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; @@ -63,12 +64,14 @@ public class AddBackupRepositoryCmd extends BaseCmd { type = CommandType.UUID, entityType = ZoneResponse.class, required = true, - description = "ID of the zone where the backup repository is to be added") + description = "ID of the zone where the backup repository is to be added for taking backups") private Long zoneId; @Parameter(name = ApiConstants.CAPACITY_BYTES, type = CommandType.LONG, description = "capacity of this backup repository") private Long capacityBytes; + @Parameter(name = ApiConstants.CROSS_ZONE_INSTANCE_CREATION, type = CommandType.BOOLEAN, description = "backups on this repository can be used to create Instances on all Zones", since = "4.22.0") + private Boolean crossZoneInstanceCreation; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -98,7 +101,7 @@ public String getProvider() { } public String getMountOptions() { - return mountOptions == null ? "" : mountOptions; + return StringUtils.isBlank(mountOptions) ? "" : mountOptions; } public Long getZoneId() { @@ -109,6 +112,10 @@ public Long getCapacityBytes() { return capacityBytes; } + public Boolean crossZoneInstanceCreationEnabled() { + return crossZoneInstanceCreation; + } + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java new file mode 100644 index 000000000000..5ffd79e497ef --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/backup/repository/UpdateBackupRepositoryCmd.java @@ -0,0 +1,116 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.backup.repository; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupRepositoryResponse; +import org.apache.cloudstack.backup.BackupRepository; +import org.apache.cloudstack.backup.BackupRepositoryService; +import org.apache.cloudstack.context.CallContext; + +import javax.inject.Inject; + +@APICommand(name = "updateBackupRepository", + description = "Update a backup repository", + responseObject = BackupRepositoryResponse.class, since = "4.22.0", + authorized = {RoleType.Admin}) +public class UpdateBackupRepositoryCmd extends BaseCmd { + + @Inject + private BackupRepositoryService backupRepositoryService; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, required = true, entityType = BackupRepositoryResponse.class, description = "ID of the backup repository") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the backup repository") + private String name; + + @Parameter(name = ApiConstants.ADDRESS, type = CommandType.STRING, description = "address of the backup repository") + private String address; + + @Parameter(name = ApiConstants.MOUNT_OPTIONS, type = CommandType.STRING, description = "shared storage mount options") + private String mountOptions; + + @Parameter(name = ApiConstants.CROSS_ZONE_INSTANCE_CREATION, type = CommandType.BOOLEAN, description = "backups in this repository can be used to create Instances on all Zones") + private Boolean crossZoneInstanceCreation; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public BackupRepositoryService getBackupRepositoryService() { + return backupRepositoryService; + } + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getAddress() { + return address; + } + + public String getMountOptions() { + return mountOptions == null ? "" : mountOptions; + } + + public Boolean crossZoneInstanceCreationEnabled() { + return crossZoneInstanceCreation; + } + + ///////////////////////////////////////////////////// + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + BackupRepository result = backupRepositoryService.updateBackupRepository(this); + if (result != null) { + BackupRepositoryResponse response = _responseGenerator.createBackupRepositoryResponse(result); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update the backup repository"); + } + } catch (Exception ex4) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex4.getMessage()); + } + + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/CreateBucketCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/CreateBucketCmd.java index d2c91e578713..099b56368676 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/CreateBucketCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/CreateBucketCmd.java @@ -72,7 +72,7 @@ public class CreateBucketCmd extends BaseAsyncCreateCmd implements UserCmd { description = "Id of the Object Storage Pool where bucket is created") private long objectStoragePoolId; - @Parameter(name = ApiConstants.QUOTA, type = CommandType.INTEGER,description = "Bucket Quota in GB") + @Parameter(name = ApiConstants.QUOTA, type = CommandType.INTEGER, required = true, description = "Bucket Quota in GiB") private Integer quota; @Parameter(name = ApiConstants.ENCRYPTION, type = CommandType.BOOLEAN, description = "Enable bucket encryption") @@ -150,7 +150,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } @@ -181,7 +181,7 @@ public void create() throws ResourceAllocationException { @Override public void execute() { - CallContext.current().setEventDetails("Bucket Id: " + getEntityUuid()); + CallContext.current().setEventDetails("Bucket ID: " + getEntityUuid()); Bucket bucket; try { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/DeleteBucketCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/DeleteBucketCmd.java index 8cd2790e4ae2..abbb1760f9d2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/DeleteBucketCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/DeleteBucketCmd.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.command.user.bucket; import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.ResourceAllocationException; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.storage.object.Bucket; import com.cloud.user.Account; @@ -82,8 +83,8 @@ public ApiCommandResourceType getApiResourceType() { } @Override - public void execute() throws ConcurrentOperationException { - CallContext.current().setEventDetails("Bucket Id: " + this._uuidMgr.getUuid(Bucket.class, getId())); + public void execute() throws ConcurrentOperationException, ResourceAllocationException { + CallContext.current().setEventDetails("Bucket ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _bucketService.deleteBucket(id, CallContext.current().getCallingAccount()); SuccessResponse response = new SuccessResponse(getCommandName()); response.setSuccess(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/UpdateBucketCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/UpdateBucketCmd.java index 8e281b20e915..dc873c300497 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/UpdateBucketCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/bucket/UpdateBucketCmd.java @@ -56,7 +56,7 @@ public class UpdateBucketCmd extends BaseCmd { @Parameter(name = ApiConstants.POLICY, type = CommandType.STRING, description = "Bucket Access Policy") private String policy; - @Parameter(name = ApiConstants.QUOTA, type = CommandType.INTEGER,description = "Bucket Quota in GB") + @Parameter(name = ApiConstants.QUOTA, type = CommandType.INTEGER, description = "Bucket Quota in GiB") private Integer quota; ///////////////////////////////////////////////////// @@ -113,7 +113,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() throws ConcurrentOperationException { - CallContext.current().setEventDetails("Bucket Id: " + this._uuidMgr.getUuid(Bucket.class, getId())); + CallContext.current().setEventDetails("Bucket ID: " + getResourceUuid(ApiConstants.ID)); boolean result = false; try { result = _bucketService.updateBucket(this, CallContext.current().getCallingAccount()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java index 7553ccffa7d4..2cb64070950b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/config/ListCapabilitiesCmd.java @@ -21,7 +21,9 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.CapabilitiesResponse; +import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.config.ApiServiceConfiguration; import com.cloud.user.Account; @@ -30,12 +32,22 @@ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class ListCapabilitiesCmd extends BaseCmd { + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "the domain for listing capabilities.", + since = "4.23.0") + private Long domainId; @Override public long getEntityOwnerId() { return Account.ACCOUNT_ID_SYSTEM; } + public Long getDomainId() { + return domainId; + } + @Override public void execute() { Map capabilities = _mgr.listCapabilities(this); @@ -51,6 +63,7 @@ public void execute() { response.setDiskOffMaxSize((Long)capabilities.get("customDiskOffMaxSize")); response.setRegionSecondaryEnabled((Boolean)capabilities.get("regionSecondaryEnabled")); response.setKVMSnapshotEnabled((Boolean)capabilities.get("KVMSnapshotEnabled")); + response.setSnapshotShowChainSize((Boolean)capabilities.get("SnapshotShowChainSize")); response.setAllowUserViewDestroyedVM((Boolean)capabilities.get("allowUserViewDestroyedVM")); response.setAllowUserExpungeRecoverVM((Boolean)capabilities.get("allowUserExpungeRecoverVM")); response.setAllowUserExpungeRecoverVolume((Boolean)capabilities.get("allowUserExpungeRecoverVolume")); @@ -72,8 +85,14 @@ public void execute() { response.setInstancesDisksStatsRetentionTime((Integer) capabilities.get(ApiConstants.INSTANCES_DISKS_STATS_RETENTION_TIME)); response.setSharedFsVmMinCpuCount((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_CPU_COUNT)); response.setSharedFsVmMinRamSize((Integer)capabilities.get(ApiConstants.SHAREDFSVM_MIN_RAM_SIZE)); + response.setInstanceLeaseEnabled((Boolean) capabilities.get(ApiConstants.INSTANCE_LEASE_ENABLED)); + response.setExtensionsPath((String)capabilities.get(ApiConstants.EXTENSIONS_PATH)); response.setDynamicScalingEnabled((Boolean) capabilities.get(ApiConstants.DYNAMIC_SCALING_ENABLED)); response.setAdditionalConfigEnabled((Boolean) capabilities.get(ApiConstants.ADDITONAL_CONFIG_ENABLED)); + if (capabilities.containsKey(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS)) { + Map vpnCustomerGatewayParameters = (Map) capabilities.get(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS); + response.setVpnCustomerGatewayParameters(vpnCustomerGatewayParameters); + } response.setObjectName("capability"); response.setResponseName(getCommandName()); this.setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java index a4c12833ee9c..41eaf36e4252 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/CreateConsoleEndpointCmd.java @@ -35,6 +35,7 @@ import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.utils.consoleproxy.ConsoleAccessUtils; import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.ObjectUtils; import javax.inject.Inject; import java.util.Map; @@ -86,6 +87,10 @@ private CreateConsoleEndpointResponse createResponse(ConsoleEndpoint endpoint) { } private ConsoleEndpointWebsocketResponse createWebsocketResponse(ConsoleEndpoint endpoint) { + if (ObjectUtils.allNull(endpoint.getWebsocketHost(), endpoint.getWebsocketPort(), endpoint.getWebsocketPath(), + endpoint.getWebsocketToken(), endpoint.getWebsocketExtra())) { + return null; + } ConsoleEndpointWebsocketResponse wsResponse = new ConsoleEndpointWebsocketResponse(); wsResponse.setHost(endpoint.getWebsocketHost()); wsResponse.setPort(endpoint.getWebsocketPort()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java new file mode 100644 index 000000000000..774cd9d59fe7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmd.java @@ -0,0 +1,182 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.import org.apache.cloudstack.context.CallContext; +package org.apache.cloudstack.api.command.user.consoleproxy; + +import org.apache.cloudstack.consoleproxy.ConsoleSession; + +import com.cloud.user.Account; +import com.cloud.user.AccountService; +import com.cloud.user.UserAccount; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.consoleproxy.ConsoleAccessManager; + +import javax.inject.Inject; +import java.util.Date; + +@APICommand(name = "listConsoleSessions", description = "Lists console sessions.", responseObject = ConsoleSessionResponse.class, + entityType = {ConsoleSession.class}, since = "4.21.0", + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.DomainAdmin, RoleType.ResourceAdmin, RoleType.User}) +public class ListConsoleSessionsCmd extends BaseListCmd { + @Inject + private AccountService accountService; + + @Inject + private ConsoleAccessManager consoleAccessManager; + + @ACL + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ConsoleSessionResponse.class, description = "The ID of the console session.") + private Long id; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "The domain ID of the account that created the console endpoint.") + private Long domainId; + + @ACL + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "The ID of the account that created the console endpoint.") + private Long accountId; + + @ACL + @Parameter(name = ApiConstants.USER_ID, type = CommandType.UUID, entityType = UserResponse.class, description = "The ID of the user that created the console endpoint.") + private Long userId; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, authorized = {RoleType.Admin}, description = "Lists console sessions from the specified host.") + private Long hostId; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, description = "Lists console sessions generated from this date onwards. " + + ApiConstants.PARAMETER_DESCRIPTION_START_DATE_POSSIBLE_FORMATS) + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, description = "Lists console sessions generated up until this date. " + + ApiConstants.PARAMETER_DESCRIPTION_END_DATE_POSSIBLE_FORMATS) + private Date endDate; + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, description = "The ID of the virtual machine.") + private Long vmId; + + @Parameter(name = ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS, type = CommandType.STRING, description = "IP address of the creator of the console endpoint.") + private String consoleEndpointCreatorAddress; + + @Parameter(name = ApiConstants.CLIENT_ADDRESS, type = CommandType.STRING, description = "IP address of the client that accessed the console session.") + private String clientAddress; + + @Parameter(name = ApiConstants.ACTIVE_ONLY, type = CommandType.BOOLEAN, + description = "Lists only active console sessions, defaults to true. Active sessions are the ones that have been acquired and have not been removed.") + private boolean activeOnly = true; + + @Parameter(name = ApiConstants.ACQUIRED, type = CommandType.BOOLEAN, + description = "Lists acquired console sessions, defaults to false. Acquired console sessions are the ones that have been accessed. " + + "The 'activeonly' parameter has precedence over the 'acquired' parameter, i.e., when the 'activeonly' parameter is 'true', the 'acquired' parameter value will be ignored.") + private boolean acquired = false; + + @Parameter(name = ApiConstants.IS_RECURSIVE, type = CommandType.BOOLEAN, + description = "Lists console sessions recursively per domain. If an account ID is informed, only the account's console sessions will be listed. Defaults to false.") + private boolean recursive = false; + + public Long getId() { + return id; + } + + public Long getDomainId() { + return domainId; + } + + public Long getAccountId() { + return accountId; + } + + public Long getUserId() { + return userId; + } + + public Long getHostId() { + return hostId; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Long getVmId() { + return vmId; + } + + public String getConsoleEndpointCreatorAddress() { + return consoleEndpointCreatorAddress; + } + + public String getClientAddress() { + return clientAddress; + } + + public boolean isActiveOnly() { + return activeOnly; + } + + public boolean getAcquired() { + return acquired; + } + + public boolean isRecursive() { + return recursive; + } + + @Override + public void execute() { + ListResponse response = consoleAccessManager.listConsoleSessions(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + if (getId() != null) { + ConsoleSession consoleSession = consoleAccessManager.listConsoleSessionById(getId()); + if (consoleSession != null) { + return consoleSession.getAccountId(); + } + } + + if (getAccountId() != null) { + return getAccountId(); + } + + if (getUserId() != null) { + UserAccount userAccount = accountService.getUserAccountById(getUserId()); + if (userAccount != null) { + return userAccount.getAccountId(); + } + } + + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java new file mode 100644 index 000000000000..298ddd64a31c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmd.java @@ -0,0 +1,169 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dns.DnsProviderType; +import org.apache.cloudstack.dns.DnsServer; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.BooleanUtils; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.EnumUtils; + +@APICommand(name = "addDnsServer", + description = "Adds a new external DNS server", + responseObject = DnsServerResponse.class, + entityType = {DnsServer.class}, + requestHasSensitiveInfo = true, + responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class AddDnsServerCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + /// + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Name of the DNS server") + private String name; + + @Parameter(name = ApiConstants.URL, type = CommandType.STRING, required = true, description = "API URL of the provider") + private String url; + + @Parameter(name = ApiConstants.PROVIDER, type = CommandType.STRING, required = true, description = "Provider type (e.g., PowerDNS)") + private String provider; + + @Parameter(name = ApiConstants.DNS_USER_NAME, type = CommandType.STRING, + description = "Username or email associated with the DNS provider account (used for authentication)") + private String dnsUserName; + + @Parameter(name = ApiConstants.DNS_API_KEY, required = true, type = CommandType.STRING, description = "API key or token for the DNS provider") + private String dnsApiKey; + + @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, description = "Port number of the external DNS server") + private Integer port; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, + description = "Whether this DNS server can be used by accounts other than the owner to create and manage DNS zones") + private Boolean isPublic; + + @Parameter(name = ApiConstants.PUBLIC_DOMAIN_SUFFIX, type = CommandType.STRING, + description = "Domain suffix that restricts DNS zones created by non-owner accounts to subdomains of this " + + "suffix (for example, sub.example.com under example.com)") + private String publicDomainSuffix; + + @Parameter(name = ApiConstants.NAME_SERVERS, type = CommandType.LIST, collectionType = CommandType.STRING, + required = true, + description = "Comma separated list of name servers; used to create NS records for the DNS Zone (for example, ns1.example.com, ns2.example.com)") + private List nameServers; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using " + + "format details[i].keyname=keyvalue. Example: details[0].pdnsServerId=localhost") + protected Map details; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { return name; } + + public String getUrl() { return url; } + + public String getDnsApiKey() { + return dnsApiKey; + } + + public Integer getPort() { + return port; + } + + public Boolean isPublic() { + return BooleanUtils.isTrue(isPublic); + } + + public String getPublicDomainSuffix() { + return publicDomainSuffix; + } + + public List getNameServers() { + return nameServers; + } + + public DnsProviderType getProvider() { + DnsProviderType dnsProviderType = EnumUtils.getEnumIgnoreCase(DnsProviderType.class, provider, DnsProviderType.PowerDNS); + if (dnsProviderType == null) { + throw new InvalidParameterValueException(String.format("Invalid value passed for provider type, valid values are: %s", + EnumUtils.listValues(DnsProviderType.values()))); + } + return dnsProviderType; + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public void execute() { + try { + DnsServer server = dnsProviderManager.addDnsServer(this); + if (server != null) { + DnsServerResponse response = dnsProviderManager.createDnsServerResponse(server); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add DNS server"); + } + } catch (Exception ex) { + logger.error("Failed to add DNS server", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + } + + public String getDnsUserName() { + return dnsUserName; + } + + public Map getDetails() { + Map detailsMap = new HashMap<>(); + if (MapUtils.isNotEmpty(details)) { + Collection props = details.values(); + for (Object prop : props) { + HashMap detail = (HashMap) prop; + for (Map.Entry entry: detail.entrySet()) { + detailsMap.put(entry.getKey(),entry.getValue()); + } + } + } + return detailsMap; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AssociateDnsZoneToNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AssociateDnsZoneToNetworkCmd.java new file mode 100644 index 000000000000..5ffffc7c304d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/AssociateDnsZoneToNetworkCmd.java @@ -0,0 +1,94 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.NetworkResponse; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.user.Account; + +@APICommand(name = "associateDnsZoneToNetwork", + description = "Associates a DNS Zone with a Network for VM auto-registration", + responseObject = DnsZoneNetworkMapResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class AssociateDnsZoneToNetworkCmd extends BaseCmd { + + @Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, + required = true, description = "The ID of the DNS zone") + private Long dnsZoneId; + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, + required = true, description = "The ID of the network") + private Long networkId; + + @Parameter(name = "subdomain", type = CommandType.STRING, + description = "Optional subdomain to append (e.g., 'dev' creates vm1.dev.example.com)") + private String subDomain; + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + DnsZoneNetworkMapResponse response = dnsProviderManager.associateZoneToNetwork(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + Network network = _entityMgr.findById(Network.class, networkId); + if (network != null) { + return network.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + public Long getDnsZoneId() { + return dnsZoneId; + } + + public Long getNetworkId() { + return networkId; + } + + public String getSubDomain() { + return subDomain; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java new file mode 100644 index 000000000000..d2fe3a5024a7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmd.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsRecordResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dns.DnsRecord; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.EnumUtils; + +@APICommand(name = "createDnsRecord", + description = "Creates a DNS record directly on the provider", + responseObject = DnsRecordResponse.class, + entityType = {DnsRecord.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateDnsRecordCmd extends BaseAsyncCmd { + + @ACL + @Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true, + description = "ID of the DNS zone") + private Long dnsZoneId; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "DNS record name") + private String name; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true, description = "DNS record type (e.g., A, AAAA, CNAME, MX, TXT, etc.)") + private String type; + + @Parameter(name = ApiConstants.CONTENTS, type = CommandType.LIST, collectionType = CommandType.STRING, required = true, + description = "The content of the record (IP address for A/AAAA, FQDN for CNAME/NS, quoted string for TXT, etc.)") + private List contents; + + @Parameter(name = "ttl", type = CommandType.INTEGER, description = "Time to live") + private Integer ttl; + + // Getters + public Long getDnsZoneId() { return dnsZoneId; } + public String getName() { return name; } + + public List getContents() { return contents; } + public Integer getTtl() { return (ttl == null) ? 3600 : ttl; } + + public DnsRecord.RecordType getType() { + DnsRecord.RecordType dnsRecordType = EnumUtils.getEnumIgnoreCase(DnsRecord.RecordType.class, type); + if (dnsRecordType == null) { + throw new InvalidParameterValueException("Invalid value passed for record type, valid values are: " + EnumUtils.listValues(DnsRecord.RecordType.values())); + } + return dnsRecordType; + } + + @Override + public void execute() { + try { + DnsRecordResponse response = dnsProviderManager.createDnsRecord(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public String getEventType() { return EventTypes.EVENT_DNS_RECORD_CREATE; } + + @Override + public String getEventDescription() { return "Creating DNS Record: " + getName(); } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java new file mode 100644 index 000000000000..10a706e3a815 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmd.java @@ -0,0 +1,154 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import java.util.Arrays; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCreateCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dns.DnsZone; +import org.apache.commons.lang3.StringUtils; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.utils.EnumUtils; + +@APICommand(name = "createDnsZone", + description = "Creates a new DNS Zone on a specific server", + responseObject = DnsZoneResponse.class, + entityType = {DnsZone.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateDnsZoneCmd extends BaseAsyncCreateCmd { + + ///////////////////////////////////////////////////// + //////////////// API Parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "The name of the DNS zone (e.g. example.com)") + private String name; + + @ACL + @Parameter(name = ApiConstants.DNS_SERVER_ID, type = CommandType.UUID, entityType = DnsServerResponse.class, + required = true, description = "The ID of the DNS server to host this zone") + private Long dnsServerId; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, + description = "The type of zone (Public, Private). Defaults to Public.") + private String type; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the DNS zone") + private String description; + + @Parameter(name = ApiConstants.EXISTING, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class, + description = "If true, imports an existing DNS zone from the DNS provider into CloudStack. " + + "If false, creates the zone in the DNS provider and registers it in CloudStack. Default is false") + private Boolean existing = false; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public Long getDnsServerId() { + return dnsServerId; + } + + public DnsZone.ZoneType getType() { + if (StringUtils.isBlank(type)) { + return DnsZone.ZoneType.Public; + } + DnsZone.ZoneType zoneType = EnumUtils.getEnumIgnoreCase(DnsZone.ZoneType.class, type); + if (zoneType == null) { + throw new IllegalArgumentException("Invalid type value, supported values are: " + Arrays.toString(DnsZone.ZoneType.values())); + } + return zoneType; + } + + public String getDescription() { + return description; + } + + ///////////////////////////////////////////////////// + /////////////// Implementation ////////////////////// + ///////////////////////////////////////////////////// + + @Override + public void create() throws ResourceAllocationException { + try { + DnsZone zone = dnsProviderManager.allocateDnsZone(this); + if (zone != null) { + setEntityId(zone.getId()); + setEntityUuid(zone.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create DNS Zone entity"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to allocate DNS Zone: " + e.getMessage()); + } + } + + @Override + public void execute() { + try { + DnsZone result = dnsProviderManager.provisionDnsZone(getEntityId(), isExistingZone()); + if (result != null) { + DnsZoneResponse response = dnsProviderManager.createDnsZoneResponse(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to provision DNS Zone on external provider"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to provision DNS Zone: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DNS_ZONE_CREATE; + } + + @Override + public String getEventDescription() { + return "creating DNS zone: " + getName(); + } + + public Boolean isExistingZone() { + return Boolean.TRUE.equals(existing); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsRecordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsRecordCmd.java new file mode 100644 index 000000000000..1d7914c346e9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsRecordCmd.java @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dns.DnsRecord; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.EnumUtils; + +@APICommand(name = "deleteDnsRecord", + description = "Deletes a DNS record from the external provider", + responseObject = SuccessResponse.class, + entityType = {DnsRecord.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteDnsRecordCmd extends BaseAsyncCmd { + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, + required = true, description = "The ID of the DNS zone") + private Long dnsZoneId; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true) + private String name; + + @Parameter(name = ApiConstants.TYPE, type = CommandType.STRING, required = true) + private String type; + + // Getters + public DnsRecord.RecordType getType() { + DnsRecord.RecordType dnsRecordType = EnumUtils.getEnumIgnoreCase(DnsRecord.RecordType.class, type); + if (dnsRecordType == null) { + throw new InvalidParameterValueException("Invalid value passed for record type, valid values are: " + EnumUtils.listValues(DnsRecord.RecordType.values())); + } + return dnsRecordType; + } + public Long getDnsZoneId() { return dnsZoneId; } + public String getName() { return name; } + + @Override + public void execute() { + try { + boolean result = dnsProviderManager.deleteDnsRecord(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS Record"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Error deleting DNS Record: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { return CallContext.current().getCallingAccount().getId(); } + + @Override + public String getEventType() { return EventTypes.EVENT_DNS_RECORD_DELETE; } + + @Override + public String getEventDescription() { return "Deleting DNS Record: " + getName(); } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java new file mode 100644 index 000000000000..099fc62f354c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmd.java @@ -0,0 +1,115 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dns.DnsServer; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +@APICommand(name = "deleteDnsServer", + description = "Removes a DNS server integration", + responseObject = SuccessResponse.class, + entityType = {DnsServer.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteDnsServerCmd extends BaseAsyncCmd { + + ///////////////////////////////////////////////////// + //////////////// API Parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsServerResponse.class, + required = true, description = "the ID of the DNS server") + private Long id; + + @Parameter(name = ApiConstants.CLEANUP, type = CommandType.BOOLEAN, + entityType = DnsZoneResponse.class, description = "If true, all associated DNS zones will be cleaned up " + + "when the server is removed. Default: true") + private Boolean cleanup = true; + + @Parameter(name = ApiConstants.UNMANAGE, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class, + description = "If true, the DNS zone is only removed from CloudStack (unmanaged); if false, it is removed " + + "from both CloudStack and the DNS provider. Default: false") + private Boolean unmanage = false; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// Implementation ////////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + boolean result = dnsProviderManager.deleteDnsServer(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS server"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS server: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + DnsServer server = _entityMgr.findById(DnsServer.class, id); + if (server != null) { + return server.getAccountId(); + } + // If server not found, return System to fail safely (or let manager handle 404) + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public String getEventType() { return EventTypes.EVENT_DNS_SERVER_DELETE; } + + @Override + public String getEventDescription() { return "Deleting DNS server ID: " + getId(); } + + public Boolean getCleanup() { + return Boolean.TRUE.equals(cleanup); + } + + public Boolean isUnmanage() { + return Boolean.TRUE.equals(unmanage); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsZoneCmd.java new file mode 100644 index 000000000000..a6d5a710ae56 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsZoneCmd.java @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dns.DnsZone; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +@APICommand(name = "deleteDnsZone", + description = "Removes a DNS Zone from CloudStack and the external provider", + responseObject = SuccessResponse.class, + entityType = {DnsZone.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteDnsZoneCmd extends BaseAsyncCmd { + + ///////////////////////////////////////////////////// + //////////////// API Parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true, + description = "The ID of the DNS zone") + private Long id; + + @Parameter(name = ApiConstants.UNMANAGE, type = CommandType.BOOLEAN, entityType = DnsZoneResponse.class, + description = "If true, removes the DNS zone only from CloudStack; if false, removes it from " + + "both CloudStack and the DNS provider. Default: false") + private Boolean unmanage = false; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation ////////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + boolean result = dnsProviderManager.deleteDnsZone(getId(), isUnmanage()); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete DNS Zone"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + DnsZone zone = _entityMgr.findById(DnsZone.class, id); + if (zone != null) { + return zone.getAccountId(); + } + // Fallback or System if not found (likely to fail in execute() anyway) + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_DNS_ZONE_DELETE; + } + + @Override + public String getEventDescription() { + return "Deleting DNS Zone ID: " + getId(); + } + + public Boolean isUnmanage() { + return Boolean.TRUE.equals(unmanage); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DisassociateDnsZoneFromNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DisassociateDnsZoneFromNetworkCmd.java new file mode 100644 index 000000000000..b0aa0902e9e6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/DisassociateDnsZoneFromNetworkCmd.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.SuccessResponse; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; +import com.cloud.user.Account; + +@APICommand(name = "disassociateDnsZoneFromNetwork", + description = "Removes the association between a DNS Zone and a Network", + responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DisassociateDnsZoneFromNetworkCmd extends BaseCmd { + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.NETWORK_ID, type = CommandType.UUID, entityType = NetworkResponse.class, + required = true, description = "The ID of the Network") + private Long networkId; + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = dnsProviderManager.disassociateZoneFromNetwork(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to disassociate DNS zone from network."); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + Network network = _entityMgr.findById(Network.class, networkId); + if (network != null) { + return network.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + public Long getNetworkId() { + return networkId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsProvidersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsProvidersCmd.java new file mode 100644 index 000000000000..800f030754b9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsProvidersCmd.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.response.DnsProviderResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dns.DnsProvider; + +@APICommand(name = "listDnsProviders", + description = "Lists available DNS plugin providers", + responseObject = DnsProviderResponse.class, + entityType = {DnsProvider.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListDnsProvidersCmd extends BaseListCmd { + + @Override + public void execute() { + List providers = dnsProviderManager.listProviderNames(); + ListResponse response = new ListResponse<>(); + List responses = new ArrayList<>(); + for (String name : providers) { + DnsProviderResponse resp = new DnsProviderResponse(name); + resp.setName(name); + responses.add(resp); + } + response.setResponses(responses); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsRecordsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsRecordsCmd.java new file mode 100644 index 000000000000..246c8bee9aab --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsRecordsCmd.java @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DnsRecordResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dns.DnsRecord; + +@APICommand(name = "listDnsRecords", + description = "Lists DNS records from the external provider", + responseObject = DnsRecordResponse.class, + entityType = {DnsRecord.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListDnsRecordsCmd extends BaseListCmd { + + @Parameter(name = ApiConstants.DNS_ZONE_ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, required = true, + description = "ID of the DNS zone to list records from") + private Long dnsZoneId; + + public Long getDnsZoneId() { + return dnsZoneId; + } + + @Override + public void execute() { + // The manager will fetch live data from the plugin + ListResponse response = dnsProviderManager.listDnsRecords(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsServersCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsServersCmd.java new file mode 100644 index 000000000000..ca7ac1944a9d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsServersCmd.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListAccountResourcesCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dns.DnsProviderType; +import org.apache.cloudstack.dns.DnsServer; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.EnumUtils; + +@APICommand(name = "listDnsServers", + description = "Lists DNS servers owned by the account.", + responseObject = DnsServerResponse.class, + entityType = {DnsServer.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListDnsServersCmd extends BaseListAccountResourcesCmd { + + ///////////////////////////////////////////////////// + //////////////// API Parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsServerResponse.class, + description = "the ID of the DNS server") + private Long id; + + @Parameter(name = ApiConstants.PROVIDER_TYPE, type = CommandType.STRING, + description = "filter by provider type (e.g. PowerDNS, Cloudflare)") + private String providerType; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public DnsProviderType getProviderType() { + DnsProviderType dnsProviderType = EnumUtils.getEnumIgnoreCase(DnsProviderType.class, providerType, DnsProviderType.PowerDNS); + if (dnsProviderType == null) { + throw new InvalidParameterValueException(String.format("Invalid value passed for provider type, valid values are: %s", + EnumUtils.listValues(DnsProviderType.values()))); + } + return dnsProviderType; + } + + ///////////////////////////////////////////////////// + /////////////// Implementation ////////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + ListResponse response = dnsProviderManager.listDnsServers(this); + response.setResponseName(getCommandName()); + response.setObjectName("dnsserver"); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsZonesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsZonesCmd.java new file mode 100644 index 000000000000..e71bdabaf617 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/ListDnsZonesCmd.java @@ -0,0 +1,63 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dns.DnsZone; + +@APICommand(name = "listDnsZones", + description = "Lists DNS zones.", responseObject = DnsZoneResponse.class, + entityType = {DnsZone.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListDnsZonesCmd extends BaseListCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, + description = "List DNS zone by ID") + private Long id; + + @Parameter(name = "dnsserverid", type = CommandType.UUID, entityType = DnsServerResponse.class, + description = "List DNS zones belonging to a specific DNS server") + private Long dnsServerId; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List by zone name") + private String name; + + public Long getId() { return id; } + public Long getDnsServerId() { return dnsServerId; } + public String getName() { return name; } + + @Override + public void execute() { + ListResponse response = dnsProviderManager.listDnsZones(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java new file mode 100644 index 000000000000..6b790fa8ade8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmd.java @@ -0,0 +1,150 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import java.util.List; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.dns.DnsServer; +import org.apache.commons.lang3.StringUtils; + +import com.cloud.user.Account; +import com.cloud.utils.EnumUtils; + +@APICommand(name = "updateDnsServer", + description = "Update DNS server", + responseObject = DnsServerResponse.class, + entityType = {DnsServer.class}, + requestHasSensitiveInfo = true, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class UpdateDnsServerCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsServerResponse.class, + required = true, description = "The ID of the DNS server to update") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Name of the DNS server") + private String name; + + @Parameter(name = ApiConstants.URL, type = CommandType.STRING, description = "API URL of the provider") + private String url; + + @Parameter(name = ApiConstants.DNS_API_KEY, type = CommandType.STRING, description = "API Key or Credentials for the external provider") + private String dnsApiKey; + + @Parameter(name = ApiConstants.PORT, type = CommandType.INTEGER, description = "Port number of the external DNS server") + private Integer port; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, + description = "Whether this DNS server can be used by accounts other than the owner to create and manage DNS zones") + private Boolean isPublic; + + @Parameter(name = ApiConstants.PUBLIC_DOMAIN_SUFFIX, type = CommandType.STRING, + description = "Domain suffix that restricts DNS zones created by non-owner accounts to subdomains of this " + + "suffix (for example, sub.example.com under example.com)") + private String publicDomainSuffix; + + @Parameter(name = ApiConstants.NAME_SERVERS, type = CommandType.LIST, collectionType = CommandType.STRING, + description = "Comma separated list of name servers; used to create NS records for the DNS Zone (for example, ns1.example.com, ns2.example.com)") + private List nameServers; + + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "Update state for the DNS server (Enabled, Disabled)") + private String state; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getId() { return id; } + public String getName() { return name; } + public String getUrl() { return url; } + public String getDnsApiKey() { + return dnsApiKey; + } + public Integer getPort() { + return port; + } + public Boolean isPublic() { + return isPublic; + } + public String getPublicDomainSuffix() { + return publicDomainSuffix; + } + public String getNameServers() { + if (nameServers == null) { + return null; + } + return StringUtils.join(nameServers.stream() + .filter(StringUtils::isNotBlank) + .map(StringUtils::trim) + .toArray(String[]::new), ","); + } + + @Override + public long getEntityOwnerId() { + DnsServer server = _entityMgr.findById(DnsServer.class, id); + if (server != null) { + return server.getAccountId(); + } + // If server not found, return System to fail safely (or let manager handle 404) + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public void execute() { + try { + DnsServer server = dnsProviderManager.updateDnsServer(this); + if (server != null) { + DnsServerResponse response = dnsProviderManager.createDnsServerResponse(server); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update DNS server"); + } + } catch (Exception ex) { + logger.error("Failed to update DNS server", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } + } + + public DnsServer.State getState() { + if (StringUtils.isBlank(state)) { + return null; + } + DnsServer.State dnsState = EnumUtils.getEnumIgnoreCase(DnsServer.State.class, state); + if (dnsState == null) { + throw new IllegalArgumentException("Invalid state value: " + state); + } + return dnsState; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java new file mode 100644 index 000000000000..5952b73ea6e4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmd.java @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.dns; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.acl.SecurityChecker; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.dns.DnsZone; + +import com.cloud.user.Account; + +@APICommand(name = "updateDnsZone", + description = "Updates a DNS Zone's metadata", + responseObject = DnsZoneResponse.class, + entityType = {DnsZone.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class UpdateDnsZoneCmd extends BaseCmd { + + ///////////////////////////////////////////////////// + //////////////// API Parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL(accessType = SecurityChecker.AccessType.OperateEntry) + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = DnsZoneResponse.class, + required = true, description = "The ID of the DNS zone") + private Long id; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, description = "The description of the DNS zone to be updated") + private String description; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getDescription() { + return description; + } + + public Long getId() { + return id; + } + + ///////////////////////////////////////////////////// + /////////////// Implementation ////////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + try { + DnsZone result = dnsProviderManager.updateDnsZone(this); + if (result != null) { + DnsZoneResponse response = dnsProviderManager.createDnsZoneResponse(result); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update DNS Zone on external provider"); + } + } catch (Exception e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update DNS Zone: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + DnsZone dnsZone = _entityMgr.findById(DnsZone.class, id); + if (dnsZone != null) { + return dnsZone.getAccountId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java index dcea200456a8..b86d1f8b956a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/event/ListEventsCmd.java @@ -73,6 +73,9 @@ public class ListEventsCmd extends BaseListProjectAndAccountResourcesCmd { @Parameter(name = ApiConstants.ARCHIVED, type = CommandType.BOOLEAN, description = "True to list archived events otherwise false", since="4.19.0") private Boolean archived; + @Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "The state of the events", since="4.21.0") + private String state; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -121,6 +124,10 @@ public boolean getArchived() { return archived != null && archived; } + public String getState() { + return state; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java index 7a96421a385b..e2c84ac85c7a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateEgressFirewallRuleCmd.java @@ -147,7 +147,7 @@ public void execute() throws ResourceUnavailableException { boolean success = false; FirewallRule rule = _entityMgr.findById(FirewallRule.class, getEntityId()); try { - CallContext.current().setEventDetails("Rule Id: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); success = _firewallService.applyEgressFirewallRules(rule, callerContext.getCallingAccount()); // State is different after the rule is applied, so get new object here rule = _entityMgr.findById(FirewallRule.class, getEntityId()); @@ -212,7 +212,7 @@ public State getState() { } @Override - public long getNetworkId() { + public Long getNetworkId() { return networkId; } @@ -255,11 +255,8 @@ public void create() { } } catch (NetworkRuleConflictException ex) { String message = "Network rule conflict: "; - if (!logger.isTraceEnabled()) { - logger.info(message + ex.getMessage()); - } else { - logger.trace(message, ex); - } + logger.error("{}{}", message, ex.getMessage()); + logger.trace(message, ex); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, ex.getMessage()); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java index 569b5fc454df..30dd1a2d015a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmd.java @@ -150,7 +150,7 @@ public void execute() throws ResourceUnavailableException { boolean success = false; FirewallRule rule = _entityMgr.findById(FirewallRule.class, getEntityId()); try { - CallContext.current().setEventDetails("Rule ID: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); success = _firewallService.applyIngressFwRules(rule.getSourceIpAddressId(), callerContext.getCallingAccount()); // State is different after the rule is applied, so get new object here @@ -223,13 +223,9 @@ public State getState() { } @Override - public long getNetworkId() { - IpAddress ip = _entityMgr.findById(IpAddress.class, getIpAddressId()); - Long ntwkId = null; - - if (ip.getAssociatedWithNetworkId() != null) { - ntwkId = ip.getAssociatedWithNetworkId(); - } + public Long getNetworkId() { + IpAddress ip = getIp(); + Long ntwkId = isVpcIp(ip) ? getVpcNetworkIdForFirewallRule(ip) : getIsolatedNetworkIdForFirewallRule(ip); if (ntwkId == null) { throw new InvalidParameterValueException("Unable to create firewall rule for the IP address ID=" + ipAddressId + @@ -238,6 +234,12 @@ public long getNetworkId() { return ntwkId; } + @Override + public Long getVpcId() { + IpAddress ip = getIp(); + return isVpcIp(ip) ? ip.getVpcId() : null; + } + @Override public long getEntityOwnerId() { Account account = CallContext.current().getCallingAccount(); @@ -271,7 +273,7 @@ public void create() { setEntityUuid(result.getUuid()); } } catch (NetworkRuleConflictException ex) { - logger.trace("Network Rule Conflict: ", ex); + logger.error("Network Rule Conflict: ", ex); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, ex.getMessage(), ex); } } @@ -300,7 +302,21 @@ public String getSyncObjType() { @Override public Long getSyncObjId() { - return getIp().getAssociatedWithNetworkId(); + Long syncObjId = getIp().getAssociatedWithNetworkId(); + return syncObjId != null ? syncObjId : getNetworkId(); + } + + private boolean isVpcIp(IpAddress ip) { + return ip.getVpcId() != null; + } + + private Long getIsolatedNetworkIdForFirewallRule(IpAddress ip) { + return ip.getAssociatedWithNetworkId(); + } + + private Long getVpcNetworkIdForFirewallRule(IpAddress ip) { + // VPC flow is independent from tier association; manager resolves execution network. + return ip.getNetworkId(); } private IpAddress getIp() { @@ -311,6 +327,7 @@ private IpAddress getIp() { return ip; } + @Override public Integer getIcmpCode() { if (icmpCode != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java index db6b788178ab..66fc118395ea 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/CreatePortForwardingRuleCmd.java @@ -54,7 +54,6 @@ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class CreatePortForwardingRuleCmd extends BaseAsyncCreateCmd implements PortForwardingRule { - // /////////////////////////////////////////////////// // ////////////// API parameters ///////////////////// // /////////////////////////////////////////////////// @@ -177,7 +176,7 @@ public Boolean getOpenFirewall() { } } - private Long getVpcId() { + public Long getVpcId() { if (ipAddressId != null) { IpAddress ipAddr = _networkService.getIp(ipAddressId); if (ipAddr == null || !ipAddr.readyToUse()) { @@ -199,7 +198,7 @@ public void execute() throws ResourceUnavailableException { boolean success = true; PortForwardingRule rule = null; try { - CallContext.current().setEventDetails("Rule Id: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); if (getOpenFirewall()) { success = success && _firewallService.applyIngressFirewallRules(ipAddressId, callerContext.getCallingAccount()); @@ -276,15 +275,9 @@ public State getState() { } @Override - public long getNetworkId() { + public Long getNetworkId() { IpAddress ip = _entityMgr.findById(IpAddress.class, getIpAddressId()); - Long ntwkId = null; - - if (ip.getAssociatedWithNetworkId() != null) { - ntwkId = ip.getAssociatedWithNetworkId(); - } else { - ntwkId = networkId; - } + Long ntwkId = _networkService.getPreferredNetworkIdForPublicIpRuleAssignment(ip, networkId); if (ntwkId == null) { throw new InvalidParameterValueException("Unable to create port forwarding rule for the ipAddress id=" + ipAddressId + " as ip is not associated with any network and no networkId is passed in"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteEgressFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteEgressFirewallRuleCmd.java index 60c7839bdc6d..4b606683a396 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteEgressFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteEgressFirewallRuleCmd.java @@ -71,7 +71,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting egress firewall rule id=" + id); + return ("Deleting egress firewall rule with ID: " + getResourceUuid(ApiConstants.ID)); } @Override @@ -89,7 +89,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule Id: " + id); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _firewallService.revokeEgressFirewallRule(id, true); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteFirewallRuleCmd.java index 7c4d5f2249f5..ff2dce8dacf0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeleteFirewallRuleCmd.java @@ -69,7 +69,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting firewall rule ID=" + id); + return ("Deleting firewall rule with ID:" + getResourceUuid(ApiConstants.ID)); } @Override @@ -87,7 +87,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule Id: " + id); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _firewallService.revokeIngressFwRule(id, true); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeletePortForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeletePortForwardingRuleCmd.java index 47dd9e039eb3..d0b607d7af48 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeletePortForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/DeletePortForwardingRuleCmd.java @@ -73,7 +73,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting port forwarding rule for ID=" + id); + return "Deleting port forwarding rule with ID:" + getResourceUuid(ApiConstants.ID); } @Override @@ -92,7 +92,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Rule ID: " + id); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); //revoke corresponding firewall rule first boolean result = _firewallService.revokeRelatedFirewallRule(id, true); result = result && _rulesService.revokePortForwardingRule(id, true); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateEgressFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateEgressFirewallRuleCmd.java index 7516a78f0bac..26d561dbe03d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateEgressFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateEgressFirewallRuleCmd.java @@ -69,7 +69,7 @@ public Boolean getDisplay() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule Id: " + id); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); FirewallRule rule = _firewallService.updateEgressFirewallRule(id, this.getCustomId(), getDisplay()); FirewallResponse fwResponse = new FirewallResponse(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateFirewallRuleCmd.java index 347434d23940..1c2ea2b1897e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/firewall/UpdateFirewallRuleCmd.java @@ -70,7 +70,7 @@ public Boolean getDisplay() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule ID: " + id); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); FirewallRule rule = _firewallService.updateIngressFirewallRule(id, this.getCustomId(), getDisplay()); FirewallResponse fwResponse = new FirewallResponse(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java new file mode 100644 index 000000000000..b035ecbe71b8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmd.java @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gpu; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.ListResponse; + +@APICommand(name = "listGpuCards", description = "Lists all available GPU cards", + responseObject = GpuCardResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0") +public class ListGpuCardsCmd extends BaseListCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + description = "ID of the GPU card") + private Long id; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, + description = "vendor name of the GPU card") + private String vendorName; + + @Parameter(name = ApiConstants.VENDOR_ID, type = CommandType.STRING, + description = "vendor ID of the GPU card") + private String vendorId; + + @Parameter(name = ApiConstants.DEVICE_ID, type = CommandType.STRING, + description = "device ID of the GPU card") + private String deviceId; + + @Parameter(name = ApiConstants.DEVICE_NAME, type = CommandType.STRING, + description = "device name of the GPU card") + private String deviceName; + + @Parameter(name = ApiConstants.ACTIVE_ONLY, type = CommandType.BOOLEAN, + description = "If true, only GPU cards which have a device will be listed. If false, all GPU cards will be listed.") + private Boolean activeOnly; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getVendorName() { + return vendorName; + } + + public String getVendorId() { + return vendorId; + } + + public String getDeviceId() { + return deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public boolean getActiveOnly() { + return Boolean.TRUE.equals(activeOnly); + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override public void execute() { + ListResponse response = gpuService.listGpuCards(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuDevicesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuDevicesCmd.java new file mode 100644 index 000000000000..19c920628519 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListGpuDevicesCmd.java @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gpu; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.context.CallContext; + +@APICommand(name = "listGpuDevices", description = "Lists all available GPU devices", + responseView = ResponseObject.ResponseView.Restricted, + responseObject = GpuDeviceResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListGpuDevicesCmd extends BaseListCmd implements UserCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, entityType = UserVmResponse.class, + description = "the virtual machine ID to which the GPU device is allocated") + private Long vmId; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getVmId() { + return vmId; + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation ////////////////// + /// ////////////////////////////////////////////////// + + @Override + public void execute() { + CallContext.current().setEventDetails("Listing GPU devices"); + ListResponse response = gpuService.listGpuDevices(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java new file mode 100644 index 000000000000..85bf91d7aeea --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmd.java @@ -0,0 +1,80 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gpu; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; + +@APICommand(name = "listVgpuProfiles", description = "Lists all available vGPU profiles", + responseObject = VgpuProfileResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0") +public class ListVgpuProfilesCmd extends BaseListCmd { + + /// ////////////////////////////////////////////////// + /// ///////////// API parameters ///////////////////// + /// ////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VgpuProfileResponse.class, + description = "ID of the vGPU profile") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the vGPU profile") + private String name; + + @Parameter(name = ApiConstants.GPU_CARD_ID, type = CommandType.UUID, entityType = GpuCardResponse.class, + description = "the GPU card ID associated with this GPU device") + private Long cardId; + + @Parameter(name = ApiConstants.ACTIVE_ONLY, type = CommandType.BOOLEAN, + description = "If true, only vGPU profiles which have a device will be listed. If false, all vGPU profiles will be listed.") + private Boolean activeOnly; + + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Long getCardId() { + return cardId; + } + + public boolean getActiveOnly() { + return Boolean.TRUE.equals(activeOnly); + } + + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// + + @Override public void execute() { + ListResponse response = gpuService.listVgpuProfiles(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java index 948b1638c8c5..61e1d7aa5192 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmd.java @@ -26,7 +26,9 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import com.cloud.cpu.CPU; import com.cloud.storage.GuestOsCategory; import com.cloud.utils.Pair; @@ -39,12 +41,48 @@ public class ListGuestOsCategoriesCmd extends BaseListCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "List Os category by id") + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "List OS category by ID") private Long id; - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List os category by name", since = "3.0.1") + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "List OS category by name", since = "3.0.1") private String name; + @Parameter(name = ApiConstants.IS_FEATURED, + type = CommandType.BOOLEAN, + description = "List available OS categories by featured or not", + since = "4.21.0") + private Boolean featured; + + @Parameter(name = ApiConstants.IS_ISO, + type = CommandType.BOOLEAN, + description = "List OS categories for which an ISO is available", + since = "4.21.0") + private Boolean iso; + + @Parameter(name = ApiConstants.IS_VNF, type = CommandType.BOOLEAN, + description = "List OS categories for which a VNF template is available", + since = "4.21.0") + private Boolean vnf; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "List available OS categories types for the zone", + since = "4.21.0") + private Long zoneId; + + @Parameter(name = ApiConstants.ARCH, + type = CommandType.STRING, + description = "List OS categories types available for given CPU architecture", + since = "4.21.0") + private String arch; + + @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, + type = CommandType.BOOLEAN, + description = "flag to display the resource image for the OS category", + since = "4.21.0") + private Boolean showIcon; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -57,6 +95,30 @@ public String getName() { return name; } + public Boolean isFeatured() { + return featured; + } + + public Boolean isIso() { + return iso; + } + + public Boolean isVnf() { + return vnf; + } + + public Long getZoneId() { + return zoneId; + } + + public CPU.CPUArch getArch() { + return arch == null ? null : CPU.CPUArch.fromType(arch); + } + + public boolean isShowIcon() { + return Boolean.TRUE.equals(showIcon); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -64,14 +126,11 @@ public String getName() { @Override public void execute() { Pair, Integer> result = _mgr.listGuestOSCategoriesByCriteria(this); - ListResponse response = new ListResponse(); - List osCatResponses = new ArrayList(); + ListResponse response = new ListResponse<>(); + List osCatResponses = new ArrayList<>(); for (GuestOsCategory osCategory : result.first()) { - GuestOSCategoryResponse categoryResponse = new GuestOSCategoryResponse(); - categoryResponse.setId(osCategory.getUuid()); - categoryResponse.setName(osCategory.getName()); - - categoryResponse.setObjectName("oscategory"); + GuestOSCategoryResponse categoryResponse = _responseGenerator.createGuestOSCategoryResponse(osCategory, + isShowIcon()); osCatResponses.add(categoryResponse); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java index 4ff2ebf0a667..d558e4c4f245 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCmd.java @@ -45,6 +45,11 @@ public class ListGuestOsCmd extends BaseListCmd { @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuestOSResponse.class, description = "List by OS type ID") private Long id; + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, + entityType = GuestOSResponse.class, since = "4.22.1", + description = "Comma separated list of OS types") + private List ids; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, description = "List by OS Category ID") private Long osCategoryId; @@ -63,6 +68,10 @@ public Long getId() { return id; } + public List getIds() { + return ids; + } + public Long getOsCategoryId() { return osCategoryId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java new file mode 100644 index 000000000000..2e3849cc8503 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/CreateGuiThemeCmd.java @@ -0,0 +1,137 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + +@APICommand(name = "createGuiTheme", description = "Creates a customized GUI theme for a set of Common Names (fixed or wildcard), a set of domain UUIDs, and/or a set of " + + "account UUIDs.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.21.0.0", authorized = {RoleType.Admin}) +public class CreateGuiThemeCmd extends BaseCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.NAME, required = true, type = CommandType.STRING, length = 2048, description = "A name to identify the theme.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, length = 4096, description = "A description for the theme.") + private String description; + + @Parameter(name = ApiConstants.CSS, type = CommandType.STRING, length = 65535, description = "The CSS to be retrieved and imported into the GUI " + + "when matching the theme access configurations.") + private String css; + + @Parameter(name = ApiConstants.JSON_CONFIGURATION, type = CommandType.STRING, length = 65535, description = "The JSON with the configurations to be " + + "retrieved and imported into the GUI when matching the theme access configurations.") + private String jsonConfiguration; + + @Parameter(name = ApiConstants.COMMON_NAMES, type = CommandType.STRING, length = 65535, description = "A set of Common Names (CN) (fixed or " + + "wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com") + private String commonNames; + + @Parameter(name = ApiConstants.LOGIN_BASE_DOMAIN, type = CommandType.STRING, length = 65535, description = "The ACS domain to be used as base " + + "for the login when accessing the GUI through the common name defined in the theme. If a common name is not defined, this parameter is ignored on the GUI.") + private String loginBaseDomain; + + @Parameter(name = ApiConstants.DOMAIN_IDS, type = CommandType.STRING, length = 65535, description = "A set of domain UUIDs (also known as ID for " + + "the end-user) separated by comma that can retrieve the theme.") + private String domainIds; + + @Parameter(name = ApiConstants.ACCOUNT_IDS, type = CommandType.STRING, length = 65535, description = "A set of account UUIDs (also known as ID for" + + " the end-user) separated by comma that can retrieve the theme.") + private String accountIds; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Defines whether a theme can be retrieved by anyone when only " + + "the `commonNames` is informed. If the `domainIds` or `accountIds` is informed, it is considered as `false`.") + private Boolean isPublic = true; + + @Parameter(name = ApiConstants.RECURSIVE_DOMAINS, type = CommandType.BOOLEAN, description = "Defines whether the subdomains of the informed domains are considered. Default " + + "value is false.") + private Boolean recursiveDomains = false; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCss() { + return css; + } + + public String getJsonConfiguration() { + return jsonConfiguration; + } + + public String getCommonNames() { + return commonNames; + } + + public String getLoginBaseDomain() { + return loginBaseDomain; + } + + public String getDomainIds() { + return domainIds; + } + + public String getAccountIds() { + return accountIds; + } + + public Boolean getPublic() { + return isPublic; + } + + public Boolean getRecursiveDomains() { + return recursiveDomains; + } + + @Override + public void execute() { + GuiThemeJoin guiThemeJoin = guiThemeService.createGuiTheme(this); + + if (guiThemeJoin == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create the GUI theme."); + } + + GuiThemeResponse response = _responseGenerator.createGuiThemeResponse(guiThemeJoin); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java new file mode 100644 index 000000000000..35a0a749aa94 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/ListGuiThemesCmd.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.AccountResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + +@APICommand(name = "listGuiThemes", description = "Lists GUI themes.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, + since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.User, RoleType.DomainAdmin, + RoleType.ResourceAdmin}) +public class ListGuiThemesCmd extends BaseListCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, description = "The theme ID.", entityType = GuiThemeResponse.class) + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "The name of the theme.") + private String name; + + @Parameter(name = ApiConstants.COMMON_NAME, type = CommandType.STRING, description = "The internet Common Name (CN) to be filtered.") + private String commonName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "The ID of the domain to be filtered.") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT_ID, type = CommandType.UUID, entityType = AccountResponse.class, description = "The ID of the account to be filtered.") + private Long accountId; + + @Parameter(name = ApiConstants.LIST_ALL, type = CommandType.BOOLEAN, description = "Whether to list all themes.") + private boolean listAll = false; + + @Parameter(name = ApiConstants.SHOW_REMOVED, type = CommandType.BOOLEAN, description = "Whether to list removed themes.") + private boolean showRemoved = false; + + @Parameter(name = ApiConstants.SHOW_PUBLIC, type = CommandType.BOOLEAN, description = "Whether to list public themes.") + private Boolean showPublic; + + @Parameter(name = ApiConstants.LIST_ONLY_DEFAULT_THEME, type = CommandType.BOOLEAN, description = "Whether to only list the default theme.") + private boolean listOnlyDefaultTheme = false; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getCommonName() { + return commonName; + } + + public Long getDomainId() { + return domainId; + } + + public Long getAccountId() { + return accountId; + } + + public boolean getListAll() { + return listAll; + } + + public boolean getShowRemoved() { + return showRemoved; + } + + public Boolean getShowPublic() { + return showPublic; + } + + public boolean getListOnlyDefaultTheme() { + return listOnlyDefaultTheme; + } + + @Override + public void execute() { + ListResponse response = guiThemeService.listGuiThemes(this); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java new file mode 100644 index 000000000000..64164838eba4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/RemoveGuiThemeCmd.java @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + +@APICommand(name = "removeGuiTheme", description = "Removes an existing GUI theme.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, + since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class RemoveGuiThemeCmd extends BaseCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = GuiThemeResponse.class, required = true, + description = "The unique identifier of the GUI theme to be removed.") + private Long id; + + public Long getId() { + return id; + } + + @Override + public void execute() { + guiThemeService.removeGuiTheme(this); + final SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + response.setSuccess(true); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java new file mode 100644 index 000000000000..e50961aa30d1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/gui/theme/UpdateGuiThemeCmd.java @@ -0,0 +1,144 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.gui.theme; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.gui.theme.GuiTheme; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; +import org.apache.cloudstack.gui.theme.GuiThemeService; + +import javax.inject.Inject; + + +@APICommand(name = "updateGuiTheme", description = "Updates an existing GUI theme.", responseObject = GuiThemeResponse.class, entityType = {GuiTheme.class}, + since = "4.21.0.0", requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin}) +public class UpdateGuiThemeCmd extends BaseCmd { + + @Inject + GuiThemeService guiThemeService; + + @Parameter(name = ApiConstants.ID, required = true, type = CommandType.UUID, entityType = GuiThemeResponse.class, description = "The ID of the theme to be updated.") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, length = 2048, description = "A name to identify the theme.") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, length = 4096, description = "A description for the theme.") + private String description; + + @Parameter(name = ApiConstants.CSS, type = CommandType.STRING, length = 65535, description = "The CSS to be retrieved and imported into the GUI " + + "when matching the theme access configurations.") + private String css; + + @Parameter(name = ApiConstants.JSON_CONFIGURATION, type = CommandType.STRING, length = 65535, description = "The JSON with the configurations to be " + + "retrieved and imported into the GUI when matching the theme access configurations.") + private String jsonConfiguration; + + @Parameter(name = ApiConstants.COMMON_NAMES, type = CommandType.STRING, length = 65535, description = "A set of Common Names (CN) (fixed or " + + "wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com") + private String commonNames; + + @Parameter(name = ApiConstants.LOGIN_BASE_DOMAIN, type = CommandType.STRING, length = 65535, description = "The ACS domain to be used as base for " + + "the login when accessing the GUI through the common name defined in the theme. If a common name is not defined, this parameter is ignored on the GUI.") + private String loginBaseDomain; + + @Parameter(name = ApiConstants.DOMAIN_IDS, type = CommandType.STRING, length = 65535, description = "A set of domain UUIDs (also known as ID for " + + "the end-user) separated by comma that can retrieve the theme.") + private String domainIds; + + @Parameter(name = ApiConstants.RECURSIVE_DOMAINS, type = CommandType.BOOLEAN, description = "Defines whether the subdomains of the informed domains are considered. Default " + + "value is false.") + private Boolean recursiveDomains = false; + + @Parameter(name = ApiConstants.ACCOUNT_IDS, type = CommandType.STRING, length = 65535, description = "A set of account UUIDs (also known as ID for" + + " the end-user) separated by comma that can retrieve the theme.") + private String accountIds; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "Defines whether a theme can be retrieved by anyone when only " + + "the `commonNames` is informed. If the `domainIds` or `accountIds` is informed, it is considered as `false`.") + private Boolean isPublic = true; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getCss() { + return css; + } + + public String getJsonConfiguration() { + return jsonConfiguration; + } + + public String getLoginBaseDomain() { + return loginBaseDomain; + } + + public String getCommonNames() { + return commonNames; + } + + public String getDomainIds() { + return domainIds; + } + + public String getAccountIds() { + return accountIds; + } + + public Boolean getRecursiveDomains() { + return recursiveDomains; + } + + public Boolean getIsPublic() { + return isPublic; + } + + @Override + public void execute() { + GuiThemeJoin guiThemeJoin = guiThemeService.updateGuiTheme(this); + + if (guiThemeJoin == null) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update the GUI theme."); + } + + GuiThemeResponse response = _responseGenerator.createGuiThemeResponse(guiThemeJoin); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java index 8db66112cdbe..237af7e4601b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/CreateIpv6FirewallRuleCmd.java @@ -232,7 +232,7 @@ public void execute() throws ResourceUnavailableException { boolean success = false; FirewallRule rule = ipv6Service.getIpv6FirewallRule(getEntityId()); try { - CallContext.current().setEventDetails("Rule ID: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); success = ipv6Service.applyIpv6FirewallRule(rule.getId()); // State is different after the rule is applied, so get new object here diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java index 19ecbda290c6..6df5ce1438a1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/DeleteIpv6FirewallRuleCmd.java @@ -66,7 +66,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting IPv6 firewall rule ID=" + id); + return "Deleting IPv6 firewall rule with ID:" + getResourceUuid(ApiConstants.ID); } @Override @@ -81,7 +81,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("IPv6 firewall rule ID: " + id); + CallContext.current().setEventDetails("IPv6 firewall rule ID: " + getResourceUuid(ApiConstants.ID)); boolean result = ipv6Service.revokeIpv6FirewallRule(id); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java index 353f28e908b5..f090de4e8849 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ipv6/UpdateIpv6FirewallRuleCmd.java @@ -156,7 +156,7 @@ public Integer getIcmpType() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule Id: " + getId()); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); FirewallRule rules = ipv6Service.updateIpv6FirewallRule(this); FirewallResponse ruleResponse = _responseGenerator.createIpv6FirewallRuleResponse(rules); setResponseObject(ruleResponse); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/AttachIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/AttachIsoCmd.java index 27026d62a674..47d8d6c35f26 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/AttachIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/AttachIsoCmd.java @@ -99,7 +99,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "attaching ISO: " + getId() + " to Instance: " + getVirtualMachineId(); + return "Attaching ISO with ID: " + getResourceUuid(ApiConstants.ID) + " to Instance with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override @@ -114,7 +114,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + getVirtualMachineId() + " ISO ID: " + getId()); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " ISO ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _templateService.attachIso(id, virtualMachineId, isForced()); if (result) { UserVm userVm = _responseGenerator.findUserVmById(virtualMachineId); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DeleteIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DeleteIsoCmd.java index b00b11ab1d31..28dfd25b2428 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DeleteIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DeleteIsoCmd.java @@ -87,7 +87,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting ISO " + getId(); + return "Deleting ISO with ID: " + getResourceUuid(ApiConstants.ID) + " from zone " + getResourceUuid(ApiConstants.ZONE_ID); } @Override @@ -102,7 +102,7 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("ISO Id: " + getId()); + CallContext.current().setEventDetails("ISO ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _templateService.deleteIso(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java index f49f3589a790..2560d837de12 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/DetachIsoCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.command.user.vm.DeployVMCmd; +import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserVmResponse; import com.cloud.event.EventTypes; @@ -51,6 +52,10 @@ public class DetachIsoCmd extends BaseAsyncCmd implements UserCmd { description = "If true, ejects the ISO before detaching on VMware. Default: false", since = "4.15.1") protected Boolean forced; + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = TemplateResponse.class, + description = "The ID of the ISO to detach. Required when the Instance has more than one ISO attached.", since = "4.23.0") + protected Long id; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -89,7 +94,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "detaching ISO from Instance: " + getVirtualMachineId(); + return "Detaching ISO from Instance with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override @@ -104,7 +109,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - boolean result = _templateService.detachIso(virtualMachineId, isForced()); + boolean result = _templateService.detachIso(virtualMachineId, id, isForced()); if (result) { UserVm userVm = _entityMgr.findById(UserVm.class, virtualMachineId); UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", userVm).get(0); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java index 6cd8b312f979..279db0f3104e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ExtractIsoCmd.java @@ -17,7 +17,6 @@ package org.apache.cloudstack.api.command.user.iso; -import com.cloud.dc.DataCenter; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -102,15 +101,13 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - String isoId = this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId()); - String baseDescription = String.format("Extracting ISO: %s", isoId); + String description = "Extracting ISO: " + getResourceUuid(ApiConstants.ID); - Long zoneId = getZoneId(); - if (zoneId == null) { - return baseDescription; + if (getZoneId() == null) { + description += "from zone: " + getResourceUuid(ApiConstants.ZONE_ID); } - return String.format("%s from zone: %s", baseDescription, this._uuidMgr.getUuid(DataCenter.class, zoneId)); + return description; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/GetUploadParamsForIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/GetUploadParamsForIsoCmd.java index 5cd7b151221c..43cdf09a89cf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/GetUploadParamsForIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/GetUploadParamsForIsoCmd.java @@ -104,6 +104,29 @@ public Long getOsTypeId() { return osTypeId; } + public void setBootable(Boolean bootable) { + this.bootable = bootable; + } + + public void setDisplayText(String displayText) { + this.displayText = displayText; + } + + public void setFeatured(Boolean featured) { + this.featured = featured; + } + + public void setPublicIso(Boolean publicIso) { + this.publicIso = publicIso; + } + + public void setExtractable(Boolean extractable) { + this.extractable = extractable; + } + + public void setOsTypeId(Long osTypeId) { + this.osTypeId = osTypeId; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// @@ -135,7 +158,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(getAccountName(), getDomainId(), getProjectId(), true); + Long accountId = _accountService.finalizeAccountId(getAccountName(), getDomainId(), getProjectId(), true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsoPermissionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsoPermissionsCmd.java index 48e4b85d66e2..069f3e4959bb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsoPermissionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsoPermissionsCmd.java @@ -1,4 +1,4 @@ -// Licensedname = "listIsoPermissions", to the Apache Software Foundation (ASF) under one +// 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 diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java index 64885b6f8337..562cbc2c623d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/ListIsosCmd.java @@ -16,11 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.iso; -import com.cloud.cpu.CPU; -import com.cloud.server.ResourceIcon; -import com.cloud.server.ResourceTag; -import org.apache.cloudstack.api.response.ResourceIconResponse; - import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -28,16 +23,17 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; +import com.cloud.server.ResourceTag; import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.user.Account; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; @APICommand(name = "listIsos", description = "Lists all available ISO files.", responseObject = TemplateResponse.class, responseView = ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -61,7 +57,7 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, description = "True if the ISO is publicly available to all Users, false otherwise.") private Boolean publicIso; - @Parameter(name = ApiConstants.IS_READY, type = CommandType.BOOLEAN, description = "True if this ISO is ready to be deployed") + @Parameter(name = ApiConstants.IS_READY, type = CommandType.BOOLEAN, description = "If True, list ISOs that are ready to be deployed.") private Boolean ready; @Parameter(name = ApiConstants.ISO_FILTER, @@ -91,10 +87,15 @@ public class ListIsosCmd extends BaseListTaggedResourcesCmd implements UserCmd { private Boolean showIcon; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64", + description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType= GuestOSCategoryResponse.class, + description = "the ID of the OS category for the ISO", + since = "4.21.0") + private Long osCategoryId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -173,6 +174,10 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Long getOsCategoryId() { + return osCategoryId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -190,24 +195,14 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { ListResponse response = _queryService.listIsos(this); - if (response != null && response.getCount() > 0 && getShowIcon()) { - updateIsoResponse(response.getResponses()); + if (response != null && getShowIcon()) { + _responseGenerator.updateTemplateIsoResponsesForIcons(response.getResponses(), + ResourceTag.ResourceObjectType.ISO); } response.setResponseName(getCommandName()); setResponseObject(response); } - private void updateIsoResponse(List response) { - for (TemplateResponse templateResponse : response) { - ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.ISO, templateResponse.getId()); - if (resourceIcon == null) { - continue; - } - ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); - templateResponse.setResourceIconResponse(iconResponse); - } - } - public Long getStoragePoolId() { return null; }; diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java index 2de0f96f2716..1c57e902e221 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/iso/RegisterIsoCmd.java @@ -120,7 +120,7 @@ public class RegisterIsoCmd extends BaseCmd implements UserCmd { private Boolean passwordEnabled; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64", + description = "the CPU arch of the ISO. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; @@ -254,7 +254,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java index b55d1b234f19..2c8401831132 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/job/ListAsyncJobsCmd.java @@ -19,6 +19,7 @@ import java.util.Date; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseListAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; @@ -40,6 +41,12 @@ public class ListAsyncJobsCmd extends BaseListAccountResourcesCmd { @Parameter(name = ApiConstants.MANAGEMENT_SERVER_ID, type = CommandType.UUID, entityType = ManagementServerResponse.class, description = "The id of the management server", since="4.19") private Long managementServerId; + @Parameter(name = ApiConstants.RESOURCE_ID, validations = {ApiArgValidator.UuidString}, type = CommandType.STRING, description = "the ID of the resource associated with the job", since="4.22.1") + private String resourceId; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the job", since="4.22.1") + private String resourceType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -52,6 +59,14 @@ public Long getManagementServerId() { return managementServerId; } + public String getResourceId() { + return resourceId; + } + + public String getResourceType() { + return resourceType; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java index 93a443757212..5c3b0084574b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/job/QueryAsyncJobResultCmd.java @@ -16,8 +16,8 @@ // under the License. package org.apache.cloudstack.api.command.user.job; - import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; @@ -34,9 +34,15 @@ public class QueryAsyncJobResultCmd extends BaseCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType = AsyncJobResponse.class, required = true, description = "The ID of the asynchronous job") + @Parameter(name = ApiConstants.JOB_ID, type = CommandType.UUID, entityType = AsyncJobResponse.class, description = "The ID of the asynchronous job") private Long id; + @Parameter(name = ApiConstants.RESOURCE_ID, validations = {ApiArgValidator.UuidString}, type = CommandType.STRING, description = "the ID of the resource associated with the job", since="4.22.1") + private String resourceId; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, description = "the type of the resource associated with the job", since="4.22.1") + private String resourceType; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -45,6 +51,14 @@ public Long getId() { return id; } + public String getResourceId() { + return resourceId; + } + + public String getResourceType() { + return resourceType; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/CreateKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/CreateKMSKeyCmd.java new file mode 100644 index 000000000000..c2b8fb6fe09e --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/CreateKMSKeyCmd.java @@ -0,0 +1,159 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.kms; + +import com.cloud.exception.ResourceAllocationException; +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "createKMSKey", + description = "Creates a new KMS key (Key Encryption Key) for encryption", + responseObject = KMSKeyResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class CreateKMSKeyCmd extends BaseCmd implements UserCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.NAME, + required = true, + type = CommandType.STRING, + description = "Name of the KMS key") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "Description of the KMS key") + private String description; + + @Parameter(name = ApiConstants.ZONE_ID, + required = true, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "Zone ID where the key will be valid") + private Long zoneId; + + @Parameter(name = ApiConstants.ACCOUNT, + type = CommandType.STRING, + description = "Account name (for creating keys for child accounts - requires domain admin or admin)") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "Domain ID (for creating keys for child accounts - requires domain admin or admin)") + private Long domainId; + + @Parameter(name = ApiConstants.PROJECT_ID, + type = CommandType.UUID, + entityType = ProjectResponse.class, + description = "ID of the project to create the KMS key for") + private Long projectId; + + @Parameter(name = ApiConstants.KEY_BITS, + type = CommandType.INTEGER, + description = "Key size in bits: 128, 192, or 256 (default: 256)") + private Integer keyBits; + + @Parameter(name = ApiConstants.HSM_PROFILE_ID, + type = CommandType.UUID, + entityType = HSMProfileResponse.class, + required = true, + description = "ID of HSM profile to create key in") + private Long hsmProfileId; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Long getZoneId() { + return zoneId; + } + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + public Long getProjectId() { + return projectId; + } + + public Integer getKeyBits() { + return keyBits != null ? keyBits : 256; + } + + public Long getHsmProfileId() { + return hsmProfileId; + } + + @Override + public void execute() throws ResourceAllocationException { + try { + KMSKeyResponse response = kmsManager.createKMSKey(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to create KMS key: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); + if (accountId != null) { + return accountId; + } + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KmsKey; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/DeleteKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/DeleteKMSKeyCmd.java new file mode 100644 index 000000000000..58f67ec60c4d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/DeleteKMSKeyCmd.java @@ -0,0 +1,104 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.kms; + +import com.cloud.event.EventTypes; +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.KMSKey; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "deleteKMSKey", + description = "Deletes a KMS key (only if not in use)", + responseObject = SuccessResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class DeleteKMSKeyCmd extends BaseAsyncCmd implements UserCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, + required = true, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "The UUID of the KMS key to delete") + private Long id; + + @Override + public void execute() { + try { + SuccessResponse response = kmsManager.deleteKMSKey(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to delete KMS key: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + KMSKey key = _entityMgr.findById(KMSKey.class, id); + if (key != null) { + return key.getAccountId(); + } + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KmsKey; + } + + @Override + public Long getApiResourceId() { + return getId(); + } + + @Override + public String getEventType() { + return EventTypes.EVENT_KMS_KEY_DELETE; + } + + @Override + public String getEventDescription() { + return "deleting KMS key: " + getId(); + } + + public Long getId() { + return id; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/ListKMSKeysCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/ListKMSKeysCmd.java new file mode 100644 index 000000000000..c6e4292099fe --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/ListKMSKeysCmd.java @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.kms; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject.ResponseView; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "listKMSKeys", + description = "Lists KMS keys available to the caller", + responseObject = KMSKeyResponse.class, + responseView = ResponseView.Restricted, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class ListKMSKeysCmd extends BaseListProjectAndAccountResourcesCmd implements UserCmd { + private static final String s_name = "listkmskeysresponse"; + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "List KMS key by UUID") + private Long id; + + @Parameter(name = ApiConstants.ZONE_ID, + type = CommandType.UUID, + entityType = ZoneResponse.class, + description = "Filter by zone ID") + private Long zoneId; + + @Parameter(name = ApiConstants.ENABLED, + type = CommandType.BOOLEAN, + description = "Filter by enabled status") + private Boolean enabled; + + @Parameter(name = ApiConstants.HSM_PROFILE_ID, + type = CommandType.UUID, + entityType = HSMProfileResponse.class, + description = "Filter by HSM profile ID") + private Long hsmProfileId; + + public Long getId() { + return id; + } + + public Long getZoneId() { + return zoneId; + } + + public Boolean getEnabled() { + return enabled; + } + + public Long getHsmProfileId() { + return hsmProfileId; + } + + @Override + public void execute() { + ListResponse listResponse = kmsManager.listKMSKeys(this); + listResponse.setResponseName(getCommandName()); + setResponseObject(listResponse); + } + + @Override + public String getCommandName() { + return s_name; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/RotateKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/RotateKMSKeyCmd.java new file mode 100644 index 000000000000..e3c6c5822b2f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/RotateKMSKeyCmd.java @@ -0,0 +1,128 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.kms; + +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.KMSKey; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "rotateKMSKey", + description = "Rotates KMS key (KEK) by creating new version and scheduling gradual re-encryption", + responseObject = AsyncJobResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class RotateKMSKeyCmd extends BaseAsyncCmd { + private static final String s_name = "rotatekmskeyresponse"; + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, + required = true, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "KMS Key UUID to rotate") + private Long id; + + @Parameter(name = ApiConstants.KEY_BITS, + type = CommandType.INTEGER, + description = "Key size for new KEK (default: same as current)") + private Integer keyBits; + + @Parameter(name = ApiConstants.HSM_PROFILE_ID, + type = CommandType.UUID, + entityType = HSMProfileResponse.class, + description = "The target HSM profile ID for the new KEK version. If provided, migrates the key to " + + "this HSM.") + private Long hsmProfileId; + + public Long getId() { + return id; + } + + public Integer getKeyBits() { + return keyBits; + } + + public Long getHsmProfileId() { + return hsmProfileId; + } + + @Override + public void execute() { + try { + kmsManager.rotateKMSKey(this); + SuccessResponse response = new SuccessResponse(); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to rotate KMS key: " + e.getMessage()); + } + } + + @Override + public String getCommandName() { + return s_name; + } + + @Override + public long getEntityOwnerId() { + KMSKey key = _entityMgr.findById(KMSKey.class, id); + if (key != null) { + return key.getAccountId(); + } + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public String getEventType() { + return com.cloud.event.EventTypes.EVENT_KMS_KEY_ROTATE; + } + + @Override + public String getEventDescription() { + return "Rotating KMS key: " + _uuidMgr.getUuid(KMSKey.class, id); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KmsKey; + } + + @Override + public Long getApiResourceId() { + return getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/UpdateKMSKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/UpdateKMSKeyCmd.java new file mode 100644 index 000000000000..77654aa13ac3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/UpdateKMSKeyCmd.java @@ -0,0 +1,113 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.kms; + +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "updateKMSKey", + description = "Updates KMS key name, description, or enabled status", + responseObject = KMSKeyResponse.class, + since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false) +public class UpdateKMSKeyCmd extends BaseCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, + required = true, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "The UUID of the KMS key to update") + private Long id; + + @Parameter(name = ApiConstants.NAME, + type = CommandType.STRING, + description = "New name for the key") + private String name; + + @Parameter(name = ApiConstants.DESCRIPTION, + type = CommandType.STRING, + description = "New description for the key") + private String description; + + @Parameter(name = ApiConstants.ENABLED, + type = CommandType.BOOLEAN, + description = "whether the key should be enabled") + private Boolean enabled; + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public Boolean getEnabled() { + return enabled; + } + + @Override + public void execute() { + try { + KMSKeyResponse response = kmsManager.updateKMSKey(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, + "Failed to update KMS key: " + e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.KmsKey; + } + + @Override + public Long getApiResourceId() { + return getId(); + } + + public Long getId() { + return id; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/CreateHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/CreateHSMProfileCmd.java new file mode 100644 index 000000000000..496ddee89b27 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/CreateHSMProfileCmd.java @@ -0,0 +1,158 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.kms.hsm; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.StringUtils; +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.HSMProfile; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; +import java.util.Map; + +@APICommand(name = "createHSMProfile", description = "Creates a new HSM profile", responseObject = HSMProfileResponse.class, + requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0", + authorized = { RoleType.Admin }) +public class CreateHSMProfileCmd extends BaseCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, + description = "the name of the HSM profile") + private String name; + + @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, + description = "the protocol of the HSM profile (PKCS11, KMIP, etc.). Default is 'pkcs11'") + private String protocol; + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, + description = "the zone ID where the HSM profile is available. If null, global scope (for admin only)") + private Long zoneId; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, + description = "the domain ID where the HSM profile is available. If specified without an account, " + + "the profile is domain-scoped and usable by all accounts directly in that domain.") + private Long domainId; + + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, + description = "the account name of the HSM profile owner. Must be used with domainId. " + + "Leave empty (with a domainId) to create a domain-scoped profile shared by all " + + "accounts in that domain.") + private String accountName; + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, + description = "the ID of the project to add the HSM profile for") + private Long projectId; + + @Parameter(name = ApiConstants.IS_PUBLIC, type = CommandType.BOOLEAN, + description = "whether this is a public HSM profile available to all users globally (root admin only). " + + "Default is false") + private Boolean isPublic; + + @Parameter(name = ApiConstants.VENDOR_NAME, type = CommandType.STRING, description = "the vendor name of the HSM") + private String vendorName; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, + description = "HSM configuration details (protocol specific)") + private Map details; + + public String getName() { + return name; + } + + public String getProtocol() { + if (StringUtils.isBlank(protocol)) { + return "pkcs11"; + } + return protocol; + } + + public Long getZoneId() { + return zoneId; + } + + public Long getDomainId() { + return domainId; + } + + public String getAccountName() { + return accountName; + } + + public Long getProjectId() { + return projectId; + } + + public Boolean getIsPublic() { + return isPublic != null && isPublic; + } + + public String getVendorName() { + return vendorName; + } + + public Map getDetails() { + return convertDetailsToMap(details); + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + HSMProfile profile = kmsManager.addHSMProfile(this); + HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); + if (accountId != null) { + return accountId; + } + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.HsmProfile; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java new file mode 100644 index 000000000000..575a4447a69a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/DeleteHSMProfileCmd.java @@ -0,0 +1,93 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.kms.hsm; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.HSMProfile; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "deleteHSMProfile", description = "Deletes an HSM profile", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", + authorized = { RoleType.Admin }) +public class DeleteHSMProfileCmd extends BaseCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true, + description = "the ID of the HSM profile") + private Long id; + + public Long getId() { + return id; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + boolean result = kmsManager.deleteHSMProfile(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete HSM profile"); + } + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + HSMProfile profile = _entityMgr.findById(HSMProfile.class, id); + if (profile != null && profile.getAccountId() > 0) { + return profile.getAccountId(); + } + return CallContext.current().getCallingAccount().getId(); + } + + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.HsmProfile; + } + + @Override + public Long getApiResourceId() { + return getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/ListHSMProfilesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/ListHSMProfilesCmd.java new file mode 100644 index 000000000000..dc5bb589c42c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/ListHSMProfilesCmd.java @@ -0,0 +1,85 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.kms.hsm; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "listHSMProfiles", description = "Lists HSM profiles", responseObject = HSMProfileResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListHSMProfilesCmd extends BaseListProjectAndAccountResourcesCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, + description = "the HSM profile ID") + private Long id; + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, + description = "the zone ID") + private Long zoneId; + + @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "the protocol of the HSM profile") + private String protocol; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "list only enabled profiles") + private Boolean enabled; + + @Parameter(name = ApiConstants.IS_PUBLIC, + type = CommandType.BOOLEAN, + description = "when true, non-admin users see only system (global) profiles") + private Boolean isPublic; + + public Long getId() { + return id; + } + + public Long getZoneId() { + return zoneId; + } + + public String getProtocol() { + return protocol; + } + + public Boolean getEnabled() { + return enabled; + } + + public Boolean getIsPublic() { + return isPublic; + } + + @Override + public void execute() { + ListResponse response = kmsManager.listHSMProfiles(this); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java new file mode 100644 index 000000000000..fea7807d616d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/kms/hsm/UpdateHSMProfileCmd.java @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.user.kms.hsm; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +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; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.kms.HSMProfile; +import org.apache.cloudstack.kms.KMSManager; + +import javax.inject.Inject; + +@APICommand(name = "updateHSMProfile", description = "Updates an HSM profile", + responseObject = HSMProfileResponse.class, + requestHasSensitiveInfo = true, responseHasSensitiveInfo = true, since = "4.23.0", + authorized = { RoleType.Admin }) +public class UpdateHSMProfileCmd extends BaseCmd { + + @Inject + private KMSManager kmsManager; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = HSMProfileResponse.class, required = true, + description = "the ID of the HSM profile") + private Long id; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "the name of the HSM profile") + private String name; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, + description = "whether the HSM profile is enabled") + private Boolean enabled; + + public Long getId() { + return id; + } + + public String getName() { + return name; + } + + public Boolean getEnabled() { + return enabled; + } + + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, + ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + try { + HSMProfile profile = kmsManager.updateHSMProfile(this); + HSMProfileResponse response = kmsManager.createHSMProfileResponse(profile); + response.setResponseName(getCommandName()); + setResponseObject(response); + } catch (KMSException e) { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, e.getMessage()); + } + } + + @Override + public long getEntityOwnerId() { + HSMProfile profile = _entityMgr.findById(HSMProfile.class, id); + if (profile != null && profile.getAccountId() > 0) { + return profile.getAccountId(); + } + return CallContext.current().getCallingAccount().getId(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.HsmProfile; + } + + @Override + public Long getApiResourceId() { + return getId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignCertToLoadBalancerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignCertToLoadBalancerCmd.java index e663e88dbdb8..c9b31dc84271 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignCertToLoadBalancerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignCertToLoadBalancerCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.response.FirewallRuleResponse; import org.apache.cloudstack.api.response.SslCertResponse; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.commons.lang3.BooleanUtils; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; @@ -57,11 +58,17 @@ public class AssignCertToLoadBalancerCmd extends BaseAsyncCmd { description = "The ID of the certificate") Long certId; + @Parameter(name = ApiConstants.FORCED, + type = CommandType.BOOLEAN, + since = "4.22", + description = "Force assign the certificate. If there is a certificate assigned to the LB, it will be removed at first.") + private Boolean forced; + @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { //To change body of implemented methods use File | Settings | File Templates. - if (_lbService.assignCertToLoadBalancer(getLbRuleId(), getCertId())) { + if (_lbService.assignCertToLoadBalancer(getLbRuleId(), getCertId(), isForced())) { SuccessResponse response = new SuccessResponse(getCommandName()); this.setResponseObject(response); } else { @@ -76,7 +83,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Assigning a certificate to a load balancer"; + return "Assigning certificate with ID: " + getResourceUuid(ApiConstants.CERTIFICATE_ID) + " to load balancer with ID: " + getResourceUuid(ApiConstants.LBID); } @Override @@ -95,4 +102,19 @@ public Long getCertId() { public Long getLbRuleId() { return lbRuleId; } + + public boolean isForced() { + return BooleanUtils.toBoolean(forced); + } + + @Override + public String getSyncObjType() { + return BaseAsyncCmd.networkSyncObject; + } + + @Override + public Long getSyncObjId() { + LoadBalancer lb = _entityMgr.findById(LoadBalancer.class, getLbRuleId()); + return (lb != null)? lb.getNetworkId(): null; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java index f7962dab1379..cc7cd2382b75 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/AssignToLoadBalancerRuleCmd.java @@ -23,6 +23,8 @@ import java.util.List; import java.util.Map; +import com.cloud.network.Network; +import com.cloud.utils.Pair; import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.api.APICommand; @@ -72,7 +74,7 @@ public class AssignToLoadBalancerRuleCmd extends BaseAsyncCmd { @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID_IP, type = CommandType.MAP, - description = "VM ID and IP map, vmidipmap[0].vmid=1 vmidipmap[0].vmip=10.1.1.75", + description = "VM ID and IP map, vmidipmap[0].vmid=1 vmidipmap[0].vmip=10.1.1.75. (Optional, for VPC Conserve Mode) Pass vmnetworkid. Example: vmidipmap[0].vmnetworkid=NETWORK_TIER_UUID", since = "4.4") private Map vmIdIpMap; @@ -112,12 +114,13 @@ public String getEventType() { @Override public String getEventDescription() { - return "applying Instances for load balancer: " + getLoadBalancerId() + " (ids: " + StringUtils.join(getVirtualMachineIds(), ",") + ")"; + return "Applying Instances for load balancer with ID: " + getResourceUuid(ApiConstants.ID) + " (Instances IDs: " + StringUtils.join(getVirtualMachineIds(), ",") + ")"; } - public Map> getVmIdIpListMap() { - Map> vmIdIpsMap = new HashMap>(); + public Pair>, Map> getVmIdIpListMapAndVmIdNetworkMap() { + Map> vmIdIpsMap = new HashMap<>(); + Map vmIdNetworkMap = new HashMap<>(); if (vmIdIpMap != null && !vmIdIpMap.isEmpty()) { Collection idIpsCollection = vmIdIpMap.values(); Iterator iter = idIpsCollection.iterator(); @@ -125,6 +128,7 @@ public Map> getVmIdIpListMap() { HashMap idIpsMap = (HashMap)iter.next(); String vmId = idIpsMap.get("vmid"); String vmIp = idIpsMap.get("vmip"); + String vmNetworkUuid = idIpsMap.get("vmnetworkid"); VirtualMachine lbvm = _entityMgr.findByUuid(VirtualMachine.class, vmId); if (lbvm == null) { @@ -145,25 +149,35 @@ public Map> getVmIdIpListMap() { ipsList = new ArrayList(); } ipsList.add(vmIp); + + if (vmNetworkUuid != null) { + Network vmNetwork = _entityMgr.findByUuid(Network.class, vmNetworkUuid); + if (vmNetwork == null) { + throw new InvalidParameterValueException("Unable to find Network ID: " + vmNetworkUuid); + } + vmIdNetworkMap.put(longVmId, vmNetwork.getId()); + } vmIdIpsMap.put(longVmId, ipsList); } } - return vmIdIpsMap; + return new Pair<>(vmIdIpsMap, vmIdNetworkMap); } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer Id: " + getLoadBalancerId() + " Instance IDs: " + StringUtils.join(getVirtualMachineIds(), ",")); + CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID) + " Instances IDs: " + StringUtils.join(getVirtualMachineIds(), ",")); - Map> vmIdIpsMap = getVmIdIpListMap(); + Pair>, Map> mapsPair = getVmIdIpListMapAndVmIdNetworkMap(); + Map> vmIdIpsMap = mapsPair.first(); + Map vmIdNetworkMap = mapsPair.second(); boolean result = false; try { - result = _lbService.assignToLoadBalancer(getLoadBalancerId(), virtualMachineIds, vmIdIpsMap, false); + result = _lbService.assignToLoadBalancer(getLoadBalancerId(), virtualMachineIds, vmIdIpsMap, vmIdNetworkMap, false); }catch (CloudRuntimeException ex) { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign load balancer rule"); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to assign load balancer rule due to: " + ex.getMessage()); } if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateApplicationLoadBalancerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateApplicationLoadBalancerCmd.java index b244375d64b9..ae9eb31a2292 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateApplicationLoadBalancerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateApplicationLoadBalancerCmd.java @@ -193,7 +193,7 @@ public long getEntityOwnerId() { public void execute() throws ResourceAllocationException, ResourceUnavailableException { ApplicationLoadBalancerRule rule = null; try { - CallContext.current().setEventDetails("Load Balancer Id: " + getEntityId()); + CallContext.current().setEventDetails("Load Balancer ID: " + getEntityUuid()); // State might be different after the rule is applied, so get new object here rule = _entityMgr.findById(ApplicationLoadBalancerRule.class, getEntityId()); ApplicationLoadBalancerResponse lbResponse = _responseGenerator.createLoadBalancerContainerReponse(rule, _lbService.getLbInstances(getEntityId())); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBHealthCheckPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBHealthCheckPolicyCmd.java index c4dfcad7918a..7b5cda13f1a5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBHealthCheckPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBHealthCheckPolicyCmd.java @@ -155,7 +155,7 @@ public void execute() throws ResourceAllocationException, ResourceUnavailableExc boolean success = false; try { - CallContext.current().setEventDetails("Load balancer health check policy ID : " + getEntityId()); + CallContext.current().setEventDetails("Load balancer health check policy ID : " + getEntityUuid()); success = _lbService.applyLBHealthCheckPolicy(this); if (success) { // State might be different after the rule is applied, so get new object here diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java index b336b84517f4..e816e0f95ebb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLBStickinessPolicyCmd.java @@ -138,7 +138,7 @@ public void execute() throws ResourceAllocationException, ResourceUnavailableExc boolean success = false; try { - CallContext.current().setEventDetails("Rule Id: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); success = _lbService.applyLBStickinessPolicy(this); if (success) { // State might be different after the rule is applied, so get new object here diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java index e0293db24999..bd72f248364e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/CreateLoadBalancerRuleCmd.java @@ -33,6 +33,7 @@ import org.apache.cloudstack.api.response.NetworkResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.lang3.StringUtils; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; @@ -112,7 +113,7 @@ public class CreateLoadBalancerRuleCmd extends BaseAsyncCreateCmd /*implements L + "rule will be created for. Required when public IP address is not associated with any Guest Network yet (VPC case)") private Long networkId; - @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "The protocol for the LB such as TCP, UDP or tcp-proxy.") + @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "The protocol for the LB such as TCP, UDP, TCP-proxy or SSL.") private String lbProtocol; @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "An optional field, whether to the display the rule to the end user or not", since = "4.4", authorized = {RoleType.Admin}) @@ -253,7 +254,7 @@ public List getSourceCidrList() { } public String getLbProtocol() { - return lbProtocol; + return StringUtils.trim(StringUtils.lowerCase(lbProtocol)); } ///////////////////////////////////////////////////// @@ -267,7 +268,7 @@ public void execute() throws ResourceAllocationException, ResourceUnavailableExc boolean success = true; LoadBalancer rule = null; try { - CallContext.current().setEventDetails("Rule Id: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); if (getOpenFirewall()) { success = success && _firewallService.applyIngressFirewallRules(getSourceIpAddressId(), callerContext.getCallingAccount()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteApplicationLoadBalancerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteApplicationLoadBalancerCmd.java index f2064d42ca4f..8cfd1876325a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteApplicationLoadBalancerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteApplicationLoadBalancerCmd.java @@ -71,12 +71,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting load balancer: " + getId(); + return "Deleting load balancer with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer ID: " + getId()); + CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _appLbService.deleteApplicationLoadBalancer(getId()); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBHealthCheckPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBHealthCheckPolicyCmd.java index 27a92bb25fc6..c01c5a4ca01e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBHealthCheckPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBHealthCheckPolicyCmd.java @@ -76,12 +76,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting load balancer health check policy: " + getId(); + return "Deleting load balancer health check policy with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer health check policy Id: " + getId()); + CallContext.current().setEventDetails("Load balancer health check policy ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _lbService.deleteLBHealthCheckPolicy(getId(), true); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBStickinessPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBStickinessPolicyCmd.java index cc83835cd0e7..f26382478f4e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBStickinessPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLBStickinessPolicyCmd.java @@ -82,12 +82,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting load balancer stickiness policy: " + getId(); + return "Deleting load balancer stickiness policy with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer stickiness policy ID: " + getId()); + CallContext.current().setEventDetails("Load balancer stickiness policy ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _lbService.deleteLBStickinessPolicy(getId(), true); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLoadBalancerRuleCmd.java index fee9067d6950..a41808ced397 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/DeleteLoadBalancerRuleCmd.java @@ -76,12 +76,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting load balancer: " + getId(); + return "Deleting load balancer with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer ID: " + getId()); + CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _firewallService.revokeRelatedFirewallRule(id, true); result = result && _lbService.deleteLoadBalancerRule(id, true); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRuleInstancesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRuleInstancesCmd.java index 8079e287c2ae..bf4604612b1d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRuleInstancesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/ListLoadBalancerRuleInstancesCmd.java @@ -95,10 +95,10 @@ public String getCommandName() { public void execute() { Pair, List> vmServiceMap = _lbService.listLoadBalancerInstances(this); List result = vmServiceMap.first(); - logger.debug(String.format("A total of [%s] user VMs were obtained when listing the load balancer instances: [%s].", result.size(), result)); + logger.debug("A total of [{}] user VMs were obtained when listing the load balancer instances: [{}].", result.size(), result); List serviceStates = vmServiceMap.second(); - logger.debug(String.format("A total of [%s] service states were obtained when listing the load balancer instances: [%s].", serviceStates.size(), serviceStates)); + logger.debug("A total of [{}] service states were obtained when listing the load balancer instances: [{}].", serviceStates.size(), serviceStates); if (!isListLbVmip()) { ListResponse response = new ListResponse<>(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveCertFromLoadBalancerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveCertFromLoadBalancerCmd.java index 3633aeca5eff..010a5ad6022d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveCertFromLoadBalancerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveCertFromLoadBalancerCmd.java @@ -67,7 +67,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing a certificate from a load balancer with ID " + getLbRuleId(); + return "Removing certificate from load balancer with ID " + getResourceUuid(ApiConstants.LBID); } @Override @@ -82,4 +82,15 @@ public long getEntityOwnerId() { public Long getLbRuleId() { return this.lbRuleId; } + + @Override + public String getSyncObjType() { + return BaseAsyncCmd.networkSyncObject; + } + + @Override + public Long getSyncObjId() { + LoadBalancer lb = _entityMgr.findById(LoadBalancer.class, getLbRuleId()); + return (lb != null)? lb.getNetworkId(): null; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveFromLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveFromLoadBalancerRuleCmd.java index 713879c8c785..ffcafd47822c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveFromLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/RemoveFromLoadBalancerRuleCmd.java @@ -143,12 +143,12 @@ public Map> getVmIdIpListMap() { @Override public String getEventDescription() { - return "removing Instances from load balancer: " + getId() + " (ids: " + StringUtils.join(getVirtualMachineIds(), ",") + ")"; + return "Removing Instances from load balancer with ID: " + getResourceUuid(ApiConstants.ID) + " (instances IDs: " + StringUtils.join(getVirtualMachineIds(), ",") + ")"; } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer Id: " + getId() + " Instance IDs: " + StringUtils.join(getVirtualMachineIds(), ",")); + CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID) + " Instances IDs: " + StringUtils.join(getVirtualMachineIds(), ",")); Map> vmIdIpsMap = getVmIdIpListMap(); try { boolean result = _lbService.removeFromLoadBalancer(id, virtualMachineIds, vmIdIpsMap, false); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateApplicationLoadBalancerCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateApplicationLoadBalancerCmd.java index 19a366732d54..c2075c2c79e0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateApplicationLoadBalancerCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateApplicationLoadBalancerCmd.java @@ -72,7 +72,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "updating load balancer: " + getId(); + return "Updating load balancer with ID: " + getResourceUuid(ApiConstants.ID); } @@ -81,7 +81,7 @@ public String getEventDescription() { ///////////////////////////////////////////////////// @Override public void execute() { - CallContext.current().setEventDetails("Load balancer ID: " + getId()); + CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID)); ApplicationLoadBalancerRule rule = _appLbService.updateApplicationLoadBalancer(getId(), this.getCustomId(), getDisplay()); ApplicationLoadBalancerResponse lbResponse = _responseGenerator.createLoadBalancerContainerReponse(rule, _lbService.getLbInstances(getId())); setResponseObject(lbResponse); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java index 80e285aec516..17bd61c502dc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBHealthCheckPolicyCmd.java @@ -63,7 +63,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Update load balancer health check policy ID= " + id; + return "Update load balancer health check policy ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java index 0163bb8a44f1..e79c1c561206 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLBStickinessPolicyCmd.java @@ -62,7 +62,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Update load balancer stickiness policy ID= " + id; + return "Update load balancer stickiness policy ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLoadBalancerRuleCmd.java index 01b15191e737..0ac99f1c760c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/loadbalancer/UpdateLoadBalancerRuleCmd.java @@ -33,6 +33,7 @@ import com.cloud.network.rules.FirewallRule; import com.cloud.network.rules.LoadBalancer; import com.cloud.user.Account; +import java.util.List; @APICommand(name = "updateLoadBalancerRule", description = "Updates load balancer", responseObject = LoadBalancerResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -64,6 +65,9 @@ public class UpdateLoadBalancerRuleCmd extends BaseAsyncCustomIdCmd { @Parameter(name = ApiConstants.PROTOCOL, type = CommandType.STRING, description = "The protocol for the LB") private String lbProtocol; + @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "the cidr list to forward traffic from", since = "4.22") + private List cidrList; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -92,6 +96,9 @@ public String getLbProtocol() { return lbProtocol; } + public List getCidrList() { + return cidrList; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -112,12 +119,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "updating load balancer rule"; + return "Updating load balancer rule with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Load balancer ID: " + getId()); + CallContext.current().setEventDetails("Load balancer ID: " + getResourceUuid(ApiConstants.ID)); LoadBalancer result = _lbService.updateLoadBalancerRule(this); if (result != null) { LoadBalancerResponse response = _responseGenerator.createLoadBalancerResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java index 1aaf5573a9dc..98487ddeb19a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/CreateIpForwardingRuleCmd.java @@ -108,7 +108,7 @@ public void execute() throws ResourceUnavailableException { boolean result = true; FirewallRule rule = null; try { - CallContext.current().setEventDetails("Rule ID: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); if (getOpenFirewall()) { result = result && _firewallService.applyIngressFirewallRules(ipAddressId, CallContext.current().getCallingAccount()); @@ -148,7 +148,7 @@ public void create() { setEntityId(rule.getId()); setEntityUuid(rule.getUuid()); } catch (NetworkRuleConflictException e) { - logger.info("Unable to create static NAT rule due to ", e); + logger.error("Unable to create static NAT rule due to ", e); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, e.getMessage()); } } @@ -229,8 +229,13 @@ public FirewallRule.State getState() { } @Override - public long getNetworkId() { - return -1; + public Long getNetworkId() { + return -1L; + } + + @Override + public Long getVpcId() { + return null; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DeleteIpForwardingRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DeleteIpForwardingRuleCmd.java index 6ca48bf36c43..ef9f428f8c8c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DeleteIpForwardingRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DeleteIpForwardingRuleCmd.java @@ -63,7 +63,7 @@ public Long getId() { @Override public void execute() { - CallContext.current().setEventDetails("Rule ID: " + id); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _firewallService.revokeRelatedFirewallRule(id, true); result = result && _rulesService.revokeStaticNatRule(id, true); @@ -95,7 +95,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting an IP forwarding 1:1 NAT rule ID:" + id); + return "Deleting IP forwarding 1:1 NAT rule with ID:" + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DisableStaticNatCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DisableStaticNatCmd.java index f3d03b8beb37..d80d63541c0f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DisableStaticNatCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/nat/DisableStaticNatCmd.java @@ -67,7 +67,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Disabling static NAT for IP ID=" + ipAddressId); + return ("Disabling static NAT for IP with ID: " + getResourceUuid(ApiConstants.IP_ADDRESS_ID)); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java index a6cd724f2d1f..1776436b31a3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLCmd.java @@ -58,7 +58,7 @@ public class CreateNetworkACLCmd extends BaseAsyncCreateCmd { private Integer publicEndPort; @Parameter(name = ApiConstants.CIDR_LIST, type = CommandType.LIST, collectionType = CommandType.STRING, description = "The CIDR list to allow traffic from/to. Multiple entries must be separated by a single comma character (,).") - private List cidrlist; + private List cidrList; @Parameter(name = ApiConstants.ICMP_TYPE, type = CommandType.INTEGER, description = "Type of the ICMP message being sent") private Integer icmpType; @@ -118,8 +118,8 @@ public void setProtocol(String protocol) { } public List getSourceCidrList() { - if (cidrlist != null) { - return cidrlist; + if (cidrList != null) { + return cidrList; } else { List oneCidrList = new ArrayList(); oneCidrList.add(NetUtils.ALL_IP4_CIDRS); @@ -238,6 +238,30 @@ public String getReason() { return reason; } + public void setCidrList(List cidrList) { + this.cidrList = cidrList; + } + + public void setIcmpType(Integer icmpType) { + this.icmpType = icmpType; + } + + public void setIcmpCode(Integer icmpCode) { + this.icmpCode = icmpCode; + } + + public void setNumber(Integer number) { + this.number = number; + } + + public void setDisplay(Boolean display) { + this.display = display; + } + + public void setReason(String reason) { + this.reason = reason; + } + @Override public void create() { NetworkACLItem result = _networkACLService.createNetworkACLItem(this); @@ -250,7 +274,7 @@ public void execute() throws ResourceUnavailableException { boolean success = false; NetworkACLItem rule = _networkACLService.getNetworkACLItem(getEntityId()); try { - CallContext.current().setEventDetails("Rule ID: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); success = _networkACLService.applyNetworkACL(rule.getAclId()); // State is different after the rule is applied, so get new object here diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLListCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLListCmd.java index 317032280f7a..31d1b49c2e71 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/CreateNetworkACLListCmd.java @@ -128,7 +128,7 @@ public long getEntityOwnerId() { } else { account = CallContext.current().getCallingAccount(); if (!Account.Type.ADMIN.equals(account.getType())) { - logger.warn(String.format("Only Root Admin can create global ACLs. Account [%s] cannot create any global ACL.", account)); + logger.error("Only Root Admin can create global ACLs. {} cannot create any global ACL.", account); throw new PermissionDeniedException("Only Root Admin can create global ACLs."); } 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 35fec1d6b3e2..79fb5f6d01cf 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) @@ -410,6 +419,27 @@ public Long getAsNumber() { ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// + + public CreateNetworkCmd() { + } + + public CreateNetworkCmd(long networkOfferingId, String name, String displayText, String gateway, String netmask, String startIp, String endIp, long domainId, + String accountName, long zoneId, String aclType, boolean subdomainAccess, boolean displayNetwork) { + this.networkOfferingId = networkOfferingId; + this.name = name; + this.displayText = displayText; + this.gateway = gateway; + this.netmask = netmask; + this.startIp = startIp; + this.endIp = endIp; + this.domainId = domainId; + this.accountName = accountName; + this.zoneId = zoneId; + this.aclType = aclType; + this.subdomainAccess = subdomainAccess; + this.displayNetwork = displayNetwork; + } + @Override public String getCommandName() { return s_name; @@ -417,7 +447,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLCmd.java index 76bbd127066c..a8506e06c4a3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLCmd.java @@ -61,7 +61,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting Network ACL ID=" + id); + return "Deleting Network ACL with ID:" + getResourceUuid(ApiConstants.ID); } @Override @@ -82,7 +82,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Network ACL item ID: " + id); + CallContext.current().setEventDetails("Network ACL item ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _networkACLService.revokeNetworkACLItem(id); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLListCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLListCmd.java index 0352a5756b18..3e3894a26864 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkACLListCmd.java @@ -61,7 +61,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting network ACL ID=" + id); + return ("Deleting network ACL with ID: " + getResourceUuid(ApiConstants.ID)); } @Override @@ -82,7 +82,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Network ACL ID: " + id); + CallContext.current().setEventDetails("Network ACL ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _networkACLService.deleteNetworkACL(id); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkCmd.java index 7063be7ee180..0543794e8bf5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/DeleteNetworkCmd.java @@ -66,7 +66,7 @@ public boolean isForced() { @Override public void execute() { - CallContext.current().setEventDetails("Network Id: " + id); + CallContext.current().setEventDetails("Network ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _networkService.deleteNetwork(id, isForced()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -93,7 +93,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting network: " + id; + return "Deleting network with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ImportNetworkACLCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ImportNetworkACLCmd.java new file mode 100644 index 000000000000..daf3633ce2a4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ImportNetworkACLCmd.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 org.apache.cloudstack.api.command.user.network; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.NetworkACLItemResponse; +import org.apache.cloudstack.api.response.NetworkACLResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.commons.collections.MapUtils; + +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.vpc.NetworkACLItem; +import com.cloud.user.Account; + +@APICommand(name = "importNetworkACL", description = "Imports Network ACL rules.", + responseObject = NetworkACLItemResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + since = "4.22.1") +public class ImportNetworkACLCmd extends BaseAsyncCmd { + + // /////////////////////////////////////////////////// + // ////////////// API parameters ///////////////////// + // /////////////////////////////////////////////////// + + @Parameter( + name = ApiConstants.ACL_ID, + type = CommandType.UUID, + entityType = NetworkACLResponse.class, + required = true, + description = "The ID of the Network ACL to which the rules will be imported" + ) + private Long aclId; + + @Parameter(name = ApiConstants.RULES, type = CommandType.MAP, required = true, + description = "Rules param list, id and protocol are must. Invalid rules will be discarded. Example: " + + "rules[0].id=101&rules[0].protocol=tcp&rules[0].traffictype=ingress&rules[0].state=active&rules[0].cidrlist=192.168.1.0/24" + + "&rules[0].tags=web&rules[0].aclid=acl-001&rules[0].aclname=web-acl&rules[0].number=1&rules[0].action=allow&rules[0].fordisplay=true" + + "&rules[0].description=allow%20web%20traffic&rules[1].id=102&rules[1].protocol=udp&rules[1].traffictype=egress&rules[1].state=enabled" + + "&rules[1].cidrlist=10.0.0.0/8&rules[1].tags=db&rules[1].aclid=acl-002&rules[1].aclname=db-acl&rules[1].number=2&rules[1].action=deny" + + "&rules[1].fordisplay=false&rules[1].description=deny%20database%20traffic") + private Map rules; + + + // /////////////////////////////////////////////////// + // ///////////////// Accessors /////////////////////// + // /////////////////////////////////////////////////// + + // Returns map, corresponds to a rule with the details in the keys: + // id, protocol, startport, endport, traffictype, state, cidrlist, tags, aclid, aclname, number, action, fordisplay, description + public Map getRules() { + return rules; + } + + public Long getAclId() { + return aclId; + } + + // /////////////////////////////////////////////////// + // ///////////// API Implementation/////////////////// + // /////////////////////////////////////////////////// + + + @Override + public void execute() throws ResourceUnavailableException { + validateParams(); + List importedRules = _networkACLService.importNetworkACLRules(this); + ListResponse response = new ListResponse<>(); + List aclResponse = new ArrayList<>(); + for (NetworkACLItem acl : importedRules) { + NetworkACLItemResponse ruleData = _responseGenerator.createNetworkACLItemResponse(acl); + aclResponse.add(ruleData); + } + response.setResponses(aclResponse, importedRules.size()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + if (account != null) { + return account.getId(); + } + return Account.ACCOUNT_ID_SYSTEM; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_NETWORK_ACL_IMPORT; + } + + @Override + public String getEventDescription() { + return "Importing ACL rules for ACL ID: " + getAclId(); + } + + + private void validateParams() { + if(MapUtils.isEmpty(rules)) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Rules parameter is empty or null"); + } + + if (getAclId() == null || _networkACLService.getNetworkACL(getAclId()) == null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable to find Network ACL with provided ACL ID"); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java index 110b4d48c124..79f2cf8c7449 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ListNetworksCmd.java @@ -53,6 +53,9 @@ public class ListNetworksCmd extends BaseListRetrieveOnlyResourceCountCmd implem @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = NetworkResponse.class, description = "List Networks by ID") private Long id; + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "list networks by name", since = "4.22.0") + private String name; + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, description = "The zone ID of the Network") private Long zoneId; @@ -125,6 +128,10 @@ public Long getId() { return id; } + public String getName() { + return name; + } + public Long getZoneId() { return zoneId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ReplaceNetworkACLListCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ReplaceNetworkACLListCmd.java index faddd340f83a..0b210b6b95d8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/ReplaceNetworkACLListCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/ReplaceNetworkACLListCmd.java @@ -76,7 +76,13 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Associating network ACL ID=" + aclId + " with network ID=" + networkId); + String description = "Associating Network ACL with ID:" + getResourceUuid(ApiConstants.ACL_ID); + + if (getNetworkId() != null) { + description += " to Network with ID:" + getResourceUuid(ApiConstants.NETWORK_ID); + } + + return description; } @Override @@ -95,7 +101,7 @@ public void execute() throws ResourceUnavailableException { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Network ID and private gateway can't be passed at the same time"); } - CallContext.current().setEventDetails("Network ACL ID: " + aclId); + CallContext.current().setEventDetails("Network ACL ID: " + getResourceUuid(ApiConstants.ACL_ID)); boolean result = false; if (getPrivateGatewayId() != null) { result = _networkACLService.replaceNetworkACLonPrivateGw(aclId, privateGatewayId); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java index c04a50cb05d8..2742e5ef6d18 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/RestartNetworkCmd.java @@ -115,7 +115,7 @@ public Long getSyncObjId() { @Override public String getEventDescription() { - return "Restarting network: " + getNetworkId(); + return "Restarting Network with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkACLItemCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkACLItemCmd.java index 9e7e1b5b8542..f1ba3a55a96c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkACLItemCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkACLItemCmd.java @@ -176,7 +176,7 @@ public String getReason() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule Id: " + getId()); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); NetworkACLItem aclItem = _networkACLService.updateNetworkACLItem(this); NetworkACLItemResponse aclResponse = _responseGenerator.createNetworkACLItemResponse(aclItem); setResponseObject(aclResponse); 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/network/routing/CreateRoutingFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/CreateRoutingFirewallRuleCmd.java index 7146d1ae1d19..85166f5ab84a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/CreateRoutingFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/CreateRoutingFirewallRuleCmd.java @@ -238,7 +238,7 @@ public void execute() throws ResourceUnavailableException { boolean success = false; FirewallRule rule = _firewallService.getFirewallRule(getEntityId()); try { - CallContext.current().setEventDetails("Rule ID: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); success = routedIpv4Manager.applyRoutingFirewallRule(rule.getId()); // State is different after the rule is applied, so get new object here @@ -250,7 +250,7 @@ public void execute() throws ResourceUnavailableException { } ruleResponse.setResponseName(getCommandName()); } catch (Exception ex) { - logger.error("Got exception when create Routing firewall rules: " + ex); + logger.error("Got exception when create Routing firewall rules: ", ex); } finally { if (!success || rule == null) { routedIpv4Manager.revokeRoutingFirewallRule(getEntityId()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmd.java index 16696f5f71b7..646b704e088c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmd.java @@ -67,7 +67,7 @@ public String getEventType() { @Override public String getEventDescription() { - return String.format("Deleting ipv4 routing firewall rule ID=%s", id); + return String.format("Deleting IPv4 routing firewall rule with ID: %s", getResourceUuid(ApiConstants.ID)); } @Override @@ -82,7 +82,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Routing firewall rule ID: " + id); + CallContext.current().setEventDetails("Routing firewall rule with ID: " + getResourceUuid(ApiConstants.ID)); boolean result = routedIpv4Manager.revokeRoutingFirewallRule(id); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/UpdateRoutingFirewallRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/UpdateRoutingFirewallRuleCmd.java index c6f6034b1ba1..3c3a07ceece1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/UpdateRoutingFirewallRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/network/routing/UpdateRoutingFirewallRuleCmd.java @@ -95,7 +95,7 @@ public String getEventDescription() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Rule Id: " + getId()); + CallContext.current().setEventDetails("Rule ID: " + getResourceUuid(ApiConstants.ID)); FirewallRule rule = routedIpv4Manager.updateRoutingFirewallRule(this); FirewallResponse ruleResponse = _responseGenerator.createFirewallResponse(rule); setResponseObject(ruleResponse); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java index ba868dfaf5e2..cd6baaf827bb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/offering/ListServiceOfferingsCmd.java @@ -26,6 +26,7 @@ import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.commons.lang3.EnumUtils; import org.apache.commons.lang3.StringUtils; @@ -110,6 +111,19 @@ public class ListServiceOfferingsCmd extends BaseListProjectAndAccountResourcesC since = "4.20.0") private Long templateId; + @Parameter(name = ApiConstants.VGPU_PROFILE_ID, + type = CommandType.UUID, + entityType = VgpuProfileResponse.class, + description = "The ID of the vGPU profile that listed offerings must support", + since = "4.21.0") + private Long vgpuProfileId; + + @Parameter(name = ApiConstants.GPU_ENABLED, + type = CommandType.BOOLEAN, + description = "Flag to indicate if the service offering supports GPU. If set to true, only service offerings that support GPU will be returned.", + since = "4.21.0") + private Boolean gpuEnabled; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -171,6 +185,30 @@ public Long getTemplateId() { return templateId; } + public Long getVgpuProfileId() { + return vgpuProfileId; + } + + public Boolean getGpuEnabled() { + return gpuEnabled; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public void setCpuNumber(Integer cpuNumber) { + this.cpuNumber = cpuNumber; + } + + public void setMemory(Integer memory) { + this.memory = memory; + } + + public void setEncryptRoot(Boolean encryptRoot) { + this.encryptRoot = encryptRoot; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java index 228afb5a1862..c6717ac659a4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/ActivateProjectCmd.java @@ -80,7 +80,7 @@ public List getEntityOwnerIds() { @Override public void execute() { - CallContext.current().setEventDetails("Project id: " + getId()); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.ID)); Project project = _projectService.activateProject(getId()); if (project != null) { ProjectResponse response = _responseGenerator.createProjectResponse(project); @@ -98,6 +98,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Activating project: " + id; + return "Activating project with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java index d4e2b8f56f91..c2a0c132448b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectCmd.java @@ -66,7 +66,7 @@ public Boolean isCleanup() { @Override public void execute() { - CallContext.current().setEventDetails("Project Id: " + id); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _projectService.deleteProject(id, isCleanup()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -83,7 +83,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting project: " + id; + return "Deleting project with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectInvitationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectInvitationCmd.java index 7fe2c3c4abc6..b1d129b8af77 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectInvitationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/DeleteProjectInvitationCmd.java @@ -59,7 +59,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Project invitation id " + id); + CallContext.current().setEventDetails("Project invitation ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _projectService.deleteProjectInvitation(id); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -76,7 +76,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Project invitatino id " + id + " is being removed"; + return "Removing project invitation with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java index 7850e7bf694b..f67d0d55587d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/SuspendProjectCmd.java @@ -60,7 +60,7 @@ public Long geId() { @Override public void execute() throws ConcurrentOperationException, ResourceUnavailableException { - CallContext.current().setEventDetails("Project Id: " + id); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.ID)); Project project = _projectService.suspendProject(id); if (project != null) { ProjectResponse response = _responseGenerator.createProjectResponse(project); @@ -78,7 +78,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Suspending project: " + id; + return "Suspending project with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java index e38f9417e8a8..2d9bb92e4a36 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectCmd.java @@ -133,7 +133,7 @@ public List getEntityOwnerIds() { @Override public void execute() throws ResourceAllocationException { - CallContext.current().setEventDetails("Project id: " + getId()); + CallContext.current().setEventDetails("Project ID: " + getResourceUuid(ApiConstants.ID)); if (getAccountName() != null && getUserId() != null) { throw new InvalidParameterValueException("Account name and User ID are mutually exclusive. Provide either Account name" + "to update Account or user ID to update the user of the project"); @@ -161,6 +161,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating project: " + id; + return "Updating project with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java index a8a6fb802fbf..34918de7339f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/project/UpdateProjectInvitationCmd.java @@ -92,7 +92,7 @@ public long getEntityOwnerId() { @Override public void execute() { - String eventDetails = "Project id: " + projectId + ";"; + String eventDetails = "Project id: " + getResourceUuid(ApiConstants.PROJECT_ID) + ";"; if (accountName != null) { eventDetails += " accountName: " + accountName + ";"; } else if (userId != null) { @@ -116,6 +116,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating project invitation for projectId " + projectId; + return "Updating project invitation for project with ID: " + getResourceUuid(ApiConstants.PROJECT_ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java index 1cda9ab2757d..8bb38d97c134 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/AssignToGlobalLoadBalancerRuleCmd.java @@ -145,13 +145,13 @@ public String getEventType() { @Override public String getEventDescription() { - return "assign load balancer rules " + StringUtils.join(getLoadBalancerRulesIds(), ",") + " to global load balancer rule " + getGlobalLoadBalancerRuleId(); + return "Assigning load balancer rules " + StringUtils.join(getLoadBalancerRulesIds(), ",") + " to global load balancer rule " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { CallContext.current().setEventDetails( - "Global Load balancer rule Id: " + getGlobalLoadBalancerRuleId() + " VmIds: " + StringUtils.join(getLoadBalancerRulesIds(), ",")); + "Global Load balancer rule ID: " + getResourceUuid(ApiConstants.ID) + " Instances IDs: " + StringUtils.join(getLoadBalancerRulesIds(), ",")); boolean result = _gslbService.assignToGlobalLoadBalancerRule(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java index df9b6b318651..2ecd8ef22e65 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/CreateGlobalLoadBalancerRuleCmd.java @@ -153,7 +153,7 @@ public void create() { GlobalLoadBalancerRule gslbRule = _gslbService.createGlobalLoadBalancerRule(this); this.setEntityId(gslbRule.getId()); this.setEntityUuid(gslbRule.getUuid()); - CallContext.current().setEventDetails("Rule Id: " + getEntityId()); + CallContext.current().setEventDetails("Rule ID: " + getEntityUuid()); } catch (Exception ex) { logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ex.getMessage()); @@ -180,7 +180,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, null, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, null, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java index 6053a11cf711..b44b547463e5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/DeleteGlobalLoadBalancerRuleCmd.java @@ -85,12 +85,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "deleting global load balancer rule: " + getGlobalLoadBalancerId(); + return "Deleting global load balancer rule with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Deleting global Load balancer rule Id: " + getGlobalLoadBalancerId()); + CallContext.current().setEventDetails("Deleting global Load balancer rule with ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _gslbService.deleteGlobalLoadBalancerRule(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java index eb72cad86f29..a0ec9a1296ab 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/RemoveFromGlobalLoadBalancerRuleCmd.java @@ -108,13 +108,13 @@ public String getEventType() { @Override public String getEventDescription() { - return "removing load balancer rules:" + StringUtils.join(getLoadBalancerRulesIds(), ",") + " from global load balancer: " + getGlobalLoadBalancerRuleId(); + return "Removing load balancer rules:" + StringUtils.join(getLoadBalancerRulesIds(), ",") + " from global load balancer: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { CallContext.current().setEventDetails( - "Global Load balancer rule Id: " + getGlobalLoadBalancerRuleId() + " VmIds: " + StringUtils.join(getLoadBalancerRulesIds(), ",")); + "Global Load balancer rule Id: " + getResourceUuid(ApiConstants.ID) + " VmIds: " + StringUtils.join(getLoadBalancerRulesIds(), ",")); boolean result = _gslbService.removeFromGlobalLoadBalancerRule(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java index 7ccf62a293af..a56672e29cac 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/region/ha/gslb/UpdateGlobalLoadBalancerRuleCmd.java @@ -107,7 +107,7 @@ public long getEntityOwnerId() { @Override public void execute() { - org.apache.cloudstack.context.CallContext.current().setEventDetails("Global Load balancer Id: " + getId()); + org.apache.cloudstack.context.CallContext.current().setEventDetails("Global Load balancer ID: " + getResourceUuid(ApiConstants.ID)); GlobalLoadBalancerRule gslbRule = _gslbService.updateGlobalLoadBalancerRule(this); if (gslbRule != null) { GlobalLoadBalancerResponse response = _responseGenerator.createGlobalLoadBalancerResponse(gslbRule); @@ -125,6 +125,6 @@ public String getEventType() { @Override public String getEventDescription() { - return "updating global load balancer rule"; + return "Updating global load balancer rule with ID: " + getResourceUuid(ApiConstants.ID); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListResourceLimitsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListResourceLimitsCmd.java index 7bae74c73a46..4bda23617092 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListResourceLimitsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/ListResourceLimitsCmd.java @@ -103,7 +103,7 @@ public String getTag() { @Override public void execute() { List result = - _resourceLimitService.searchForLimits(id, _accountService.finalyzeAccountId(this.getAccountName(), this.getDomainId(), this.getProjectId(), false), this.getDomainId(), + _resourceLimitService.searchForLimits(id, _accountService.finalizeAccountId(this.getAccountName(), this.getDomainId(), this.getProjectId(), false), this.getDomainId(), getResourceTypeEnum(), getTag(), this.getStartIndex(), this.getPageSizeVal()); ListResponse response = new ListResponse(); List limitResponses = new ArrayList(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java index 123b0e48a74c..d43bb29e9d27 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceCountCmd.java @@ -127,7 +127,7 @@ public long getEntityOwnerId() { @Override public void execute() { List result = - _resourceLimitService.recalculateResourceCount(_accountService.finalyzeAccountId(accountName, domainId, projectId, true), getDomainId(), getResourceType(), getTag()); + _resourceLimitService.recalculateResourceCount(_accountService.finalizeAccountId(accountName, domainId, projectId, true), getDomainId(), getResourceType(), getTag()); if ((result != null) && (result.size() > 0)) { ListResponse response = new ListResponse(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java index 3678e885a6ea..f88ef9678e31 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/resource/UpdateResourceLimitCmd.java @@ -100,7 +100,7 @@ public Integer getResourceType() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } @@ -110,7 +110,7 @@ public long getEntityOwnerId() { @Override public void execute() { - ResourceLimit result = _resourceLimitService.updateResourceLimit(_accountService.finalyzeAccountId(accountName, domainId, projectId, true), getDomainId(), resourceType, max, getTag()); + ResourceLimit result = _resourceLimitService.updateResourceLimit(_accountService.finalizeAccountId(accountName, domainId, projectId, true), getDomainId(), resourceType, max, getTag()); if (result != null || (result == null && max != null && max.longValue() == -1L)) { ResourceLimitResponse response = _responseGenerator.createResourceLimitResponse(result); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.java new file mode 100644 index 000000000000..f6268106c317 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/CreateResourceScheduleCmd.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 org.apache.cloudstack.api.command.user.schedule; + +import com.cloud.exception.InvalidParameterValueException; +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.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.schedule.ResourceScheduleManager; +import org.apache.commons.lang3.EnumUtils; + +import javax.inject.Inject; +import java.util.Date; +import java.util.Map; + +@APICommand(name = "createResourceSchedule", description = "Create Resource Schedule", responseObject = ResourceScheduleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateResourceScheduleCmd extends BaseCmd { + + @Inject + ResourceScheduleManager resourceScheduleManager; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource") + private String resourceType; + + @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedule is to be defined") + private String resourceId; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule") + private String description; + + @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = true, description = "Schedule for action on resource in cron format. e.g. '0 15 10 * *' for 'at 15:00 on 10th day of every month'") + private String schedule; + + @Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = true, description = "Specifies a timezone for this command. For more information on the timezone parameter, see TimeZone Format.") + private String timeZone; + + @Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = true, description = "Action to take on the resource.") + private String action; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute. (Format \"yyyy-MM-dd hh:mm:ss\")") + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive. (Format \"yyyy-MM-dd hh:mm:ss\")") + private Date endDate; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable schedule. Defaults to true") + private Boolean enabled; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.") + private Map details; + + public ApiCommandResourceType getResourceType() { + ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType); + if (type == null) { + throw new InvalidParameterValueException("Unknown resource type: " + resourceType); + } + return type; + } + + public String getResourceId() { + return resourceId; + } + + public String getDescription() { + return description; + } + + public String getSchedule() { + return schedule; + } + + public String getTimeZone() { + return timeZone; + } + + public String getAction() { + return action; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Boolean getEnabled() { + if (enabled == null) { + enabled = true; + } + return enabled; + } + + public Map getDetails() { + return convertDetailsToMap(details); + } + + @Override + public void execute() { + ResourceScheduleResponse response = resourceScheduleManager.createSchedule(getResourceType(), getResourceId(), + getDescription(), getSchedule(), getTimeZone(), getAction(), getStartDate(), getEndDate(), getEnabled(), getDetails()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java new file mode 100644 index 000000000000..fe9a695df637 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/DeleteResourceScheduleCmd.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.schedule; + +import com.cloud.exception.InvalidParameterValueException; +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.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.schedule.ResourceScheduleManager; +import org.apache.commons.lang3.EnumUtils; + +import javax.inject.Inject; +import java.util.List; + +@APICommand(name = "deleteResourceSchedule", description = "Delete Resource Schedule", responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteResourceScheduleCmd extends BaseCmd { + + @Inject + ResourceScheduleManager resourceScheduleManager; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource") + private String resourceType; + + @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be deleted") + private String resourceId; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule to be deleted") + private Long id; + + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids to be deleted") + private List ids; + + public ApiCommandResourceType getResourceType() { + ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType); + if (type == null) { + throw new InvalidParameterValueException("Unknown resource type: " + resourceType); + } + return type; + } + + public String getResourceId() { + return resourceId; + } + + public Long getId() { + return id; + } + + public List getIds() { + return ids; + } + + @Override + public void execute() { + resourceScheduleManager.removeSchedule(getResourceType(), getResourceId(), getId(), getIds()); + SuccessResponse response = new SuccessResponse(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java new file mode 100644 index 000000000000..1c8869eff9c3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/ListResourceScheduleCmd.java @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.schedule; + +import com.cloud.exception.InvalidParameterValueException; +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.BaseListCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; +import org.apache.cloudstack.schedule.ResourceScheduleManager; +import org.apache.commons.lang3.EnumUtils; + +import javax.inject.Inject; +import java.util.List; + +@APICommand(name = "listResourceSchedule", description = "List Resource Schedules", responseObject = ResourceScheduleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListResourceScheduleCmd extends BaseListCmd { + + @Inject + ResourceScheduleManager resourceScheduleManager; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "ID of the schedule") + private Long id; + + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = false, description = "comma separated list of schedule ids") + private List ids; + + @Parameter(name = ApiConstants.RESOURCE_TYPE, type = CommandType.STRING, required = true, description = "Type of the resource.") + private String resourceType; + + @Parameter(name = ApiConstants.RESOURCE_ID, type = CommandType.STRING, required = true, description = "ID of the resource for which schedules are to be listed.") + private String resourceId; + + @Parameter(name = ApiConstants.ACTION, type = CommandType.STRING, required = false, description = "Action to take on the resource.") + private String action; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Filter by enabled status.") + private Boolean enabled; + + public Long getId() { + return id; + } + + public List getIds() { + return ids; + } + + public ApiCommandResourceType getResourceType() { + ApiCommandResourceType type = EnumUtils.getEnumIgnoreCase(ApiCommandResourceType.class, resourceType); + if (type == null) { + throw new InvalidParameterValueException("Unknown resource type: " + resourceType); + } + return type; + } + + public String getResourceId() { + return resourceId; + } + + public String getAction() { + return action; + } + + public Boolean getEnabled() { + return enabled; + } + + @Override + public void execute() { + ListResponse response = resourceScheduleManager.listSchedule( + getId(), getIds(), getResourceType(), getResourceId(), getAction(), getEnabled(), + getStartIndex(), getPageSizeVal() + ); + response.setResponseName(getCommandName()); + setResponseObject(response); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java new file mode 100644 index 000000000000..5422588853e9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/schedule/UpdateResourceScheduleCmd.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.schedule; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.schedule.ResourceScheduleManager; + +import javax.inject.Inject; +import java.util.Date; +import java.util.Map; + +@APICommand(name = "updateResourceSchedule", description = "Update Resource Schedule", responseObject = ResourceScheduleResponse.class, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.23.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class UpdateResourceScheduleCmd extends BaseCmd { + + @Inject + ResourceScheduleManager resourceScheduleManager; + + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = ResourceScheduleResponse.class, required = true, description = "ID of the schedule to be updated") + private Long id; + + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the schedule") + private String description; + + @Parameter(name = ApiConstants.SCHEDULE, type = CommandType.STRING, required = false, description = "Schedule for action on resource in cron format.") + private String schedule; + + @Parameter(name = ApiConstants.TIMEZONE, type = CommandType.STRING, required = false, description = "Specifies a timezone for this command.") + private String timeZone; + + @Parameter(name = ApiConstants.START_DATE, type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active.") + private Date startDate; + + @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive.") + private Date endDate; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, description = "Enable or disable the schedule.") + private Boolean enabled; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, required = false, description = "Map of (key/value pairs) details for the schedule.") + private Map details; + + public Long getId() { + return id; + } + + public String getDescription() { + return description; + } + + public String getSchedule() { + return schedule; + } + + public String getTimeZone() { + return timeZone; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Boolean getEnabled() { + return enabled; + } + + public Map getDetails() { + return convertDetailsToMap(details); + } + + @Override + public void execute() { + ResourceScheduleResponse response = resourceScheduleManager.updateSchedule(getId(), getDescription(), getSchedule(), + getTimeZone(), getStartDate(), getEndDate(), getEnabled(), getDetails()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + return CallContext.current().getCallingAccount().getAccountId(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupEgressCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupEgressCmd.java index 13b09c7e19ac..7d0004c8e5d5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupEgressCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupEgressCmd.java @@ -166,7 +166,7 @@ public static String getResultObjectName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupIngressCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupIngressCmd.java index 07a4df9eb5d9..d7a95d8204e6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupIngressCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/AuthorizeSecurityGroupIngressCmd.java @@ -166,7 +166,7 @@ public static String getResultObjectName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/DeleteSecurityGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/DeleteSecurityGroupCmd.java index 0636f03c92a3..1882d80c1c1b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/DeleteSecurityGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/DeleteSecurityGroupCmd.java @@ -103,7 +103,7 @@ public Long getId() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupEgressCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupEgressCmd.java index bf435406174c..91f2b7ad999a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupEgressCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupEgressCmd.java @@ -82,7 +82,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "revoking egress rule id: " + getId(); + return "Revoking egress rule with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupIngressCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupIngressCmd.java index c426647fe36c..2d7e591214df 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupIngressCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/securitygroup/RevokeSecurityGroupIngressCmd.java @@ -83,7 +83,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "revoking ingress rule id: " + getId(); + return "Revoking ingress rule with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java index c6ed36ccef53..cae2a32a09fd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ArchiveSnapshotCmd.java @@ -54,12 +54,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "Archiving Snapshot " + id + " to secondary storage"; + return "Archiving Snapshot with ID: " + getResourceUuid(ApiConstants.ID) + " to secondary storage"; } @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { - CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class,getId())); + CallContext.current().setEventDetails("Snapshot ID: " + getResourceUuid(ApiConstants.ID)); Snapshot snapshot = _snapshotService.archiveSnapshot(getId()); if (snapshot != null) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java index 07973fcbfca5..c67439a2ef7c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmd.java @@ -17,9 +17,13 @@ package org.apache.cloudstack.api.command.user.snapshot; -import java.util.ArrayList; -import java.util.List; - +import com.cloud.dc.DataCenter; +import com.cloud.event.EventTypes; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.exception.StorageUnavailableException; +import com.cloud.storage.Snapshot; +import com.cloud.user.Account; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -31,26 +35,24 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; import org.apache.commons.collections.CollectionUtils; - -import com.cloud.dc.DataCenter; -import com.cloud.event.EventTypes; -import com.cloud.exception.ResourceAllocationException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.exception.StorageUnavailableException; -import com.cloud.storage.Snapshot; -import com.cloud.user.Account; +import org.apache.commons.lang3.BooleanUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import java.util.ArrayList; +import java.util.List; + @APICommand(name = "copySnapshot", description = "Copies a snapshot from one zone to another.", responseObject = SnapshotResponse.class, responseView = ResponseObject.ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd { public static final Logger logger = LogManager.getLogger(CopySnapshotCmd.class.getName()); + private Snapshot snapshot; ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// @@ -84,6 +86,24 @@ public class CopySnapshotCmd extends BaseAsyncCmd implements UserCmd { "Do not specify destzoneid and destzoneids together, however one of them is required.") protected List destZoneIds; + @Parameter(name = ApiConstants.STORAGE_ID_LIST, + type=CommandType.LIST, + collectionType = CommandType.UUID, + entityType = StoragePoolResponse.class, + required = false, + authorized = RoleType.Admin, + since = "4.21.0", + description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + + "The snapshot will always be made available in the zone in which the volume is present. Currently supported for StorPool only") + protected List storagePoolIds; + + @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, + type=CommandType.BOOLEAN, + since = "4.21.0", + description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " + + "This is supported only for StorPool storage for now.") + protected Boolean useStorageReplication; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -106,7 +126,15 @@ public List getDestinationZoneIds() { destIds.add(destZoneId); return destIds; } - return null; + return new ArrayList<>(); + } + + public List getStoragePoolIds() { + return storagePoolIds; + } + + public Boolean useStorageReplication() { + return BooleanUtils.toBoolean(useStorageReplication); } @Override @@ -116,18 +144,22 @@ public String getEventType() { @Override public String getEventDescription() { - StringBuilder descBuilder = new StringBuilder(); + StringBuilder descBuilder = new StringBuilder("Copying snapshot with ID: " + getResourceUuid(ApiConstants.ID)); + if (getDestinationZoneIds() != null) { + descBuilder.append(" to zones: ["); + for (Long destId : getDestinationZoneIds()) { - descBuilder.append(", "); descBuilder.append(_uuidMgr.getUuid(DataCenter.class, destId)); + descBuilder.append(", "); } - if (descBuilder.length() > 0) { - descBuilder.deleteCharAt(0); - } + + descBuilder.deleteCharAt(descBuilder.length() - 1); + + descBuilder.append("]"); } - return "copying snapshot: " + _uuidMgr.getUuid(Snapshot.class, getId()) + ((descBuilder.length() > 0) ? " to zones: " + descBuilder.toString() : ""); + return descBuilder.toString(); } @Override @@ -152,7 +184,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { try { - if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds)) + if (destZoneId == null && CollectionUtils.isEmpty(destZoneIds) && useStorageReplication()) throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Either destzoneid or destzoneids parameters have to be specified."); @@ -161,7 +193,7 @@ public void execute() throws ResourceUnavailableException { "Both destzoneid and destzoneids cannot be specified at the same time."); CallContext.current().setEventDetails(getEventDescription()); - Snapshot snapshot = _snapshotService.copySnapshot(this); + snapshot = _snapshotService.copySnapshot(this); if (snapshot != null) { SnapshotResponse response = _queryService.listSnapshot(this); @@ -177,6 +209,13 @@ public void execute() throws ResourceUnavailableException { logger.warn("Exception: ", ex); throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); } + } + + public Snapshot getSnapshot() { + return snapshot; + } + public void setSnapshot(Snapshot snapshot) { + this.snapshot = snapshot; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java index 078d4517f95e..d03df501847a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotCmd.java @@ -16,11 +16,13 @@ // under the License. package org.apache.cloudstack.api.command.user.snapshot; +import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -32,6 +34,7 @@ import org.apache.cloudstack.api.response.DomainResponse; import org.apache.cloudstack.api.response.SnapshotPolicyResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.commons.collections.MapUtils; @@ -99,6 +102,22 @@ public class CreateSnapshotCmd extends BaseAsyncCreateCmd { since = "4.19.0") protected List zoneIds; + @Parameter(name = ApiConstants.STORAGE_ID_LIST, + type=CommandType.LIST, + collectionType = CommandType.UUID, + entityType = StoragePoolResponse.class, + authorized = RoleType.Admin, + description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + + "The snapshot will always be made available in the zone in which the volume is present.", + since = "4.21.0") + protected List storagePoolIds; + + @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, + type=CommandType.BOOLEAN, + description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " + + "This is supported only for StorPool storage for now.") + protected Boolean useStorageReplication; + private String syncObjectType = BaseAsyncCmd.snapshotHostSyncObject; // /////////////////////////////////////////////////// @@ -161,6 +180,17 @@ public List getZoneIds() { return zoneIds; } + public List getStoragePoolIds() { + return storagePoolIds == null ? new ArrayList<>() : storagePoolIds; + } + + public Boolean useStorageReplication() { + if (useStorageReplication == null) { + return false; + } + return useStorageReplication; + } + // /////////////////////////////////////////////////// // ///////////// API Implementation/////////////////// // /////////////////////////////////////////////////// @@ -199,7 +229,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating Snapshot for volume: " + getVolumeUuid(); + return "Creating Snapshot for volume: " + getResourceUuid(ApiConstants.VOLUME_ID); } @Override @@ -209,12 +239,12 @@ public ApiCommandResourceType getApiResourceType() { @Override public void create() throws ResourceAllocationException { - Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds()); + Snapshot snapshot = _volumeService.allocSnapshot(getVolumeId(), getPolicyId(), getSnapshotName(), getLocationType(), getZoneIds(), getStoragePoolIds(), useStorageReplication()); if (snapshot != null) { setEntityId(snapshot.getId()); setEntityUuid(snapshot.getUuid()); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Snapshot for volume" + getVolumeUuid()); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create Snapshot for volume" + getResourceUuid(ApiConstants.VOLUME_ID)); } } @@ -223,27 +253,27 @@ public void execute() { Snapshot snapshot; try { snapshot = - _volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds()); + _volumeService.takeSnapshot(getVolumeId(), getPolicyId(), getEntityId(), _accountService.getAccount(getEntityOwnerId()), getQuiescevm(), getLocationType(), getAsyncBackup(), getTags(), getZoneIds(), getStoragePoolIds(), useStorageReplication()); if (snapshot != null) { SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot); response.setResponseName(getCommandName()); setResponseObject(response); } else { - throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Snapshot from volume [%s] was not found in database.", getVolumeUuid())); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, String.format("Snapshot from volume [%s] was not found in database.", getResourceUuid(ApiConstants.VOLUME_ID))); } } catch (Exception e) { if (e.getCause() instanceof UnsupportedOperationException) { throw new ServerApiException(ApiErrorCode.UNSUPPORTED_ACTION_ERROR, String.format("Failed to create Snapshot due to unsupported operation: %s", e.getCause().getMessage())); } - String errorMessage = "Failed to create Snapshot due to an internal error creating Snapshot for volume " + getVolumeUuid(); + String errorMessage = "Failed to create Snapshot due to an internal error creating Snapshot for volume " + getResourceUuid(ApiConstants.VOLUME_ID); logger.error(errorMessage, e); throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, errorMessage); } } - private Snapshot.LocationType getLocationType() { + public Snapshot.LocationType getLocationType() { if (locationType == null) { return null; } @@ -282,8 +312,4 @@ public Boolean getAsyncBackup() { return asyncBackup; } } - - protected String getVolumeUuid() { - return _uuidMgr.getUuid(Volume.class, getVolumeId()); - } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java index 6b7e9e83d62e..6fb5fb0463ab 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotFromVMSnapshotCmd.java @@ -143,7 +143,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating Snapshot from Instance Snapshot : " + this._uuidMgr.getUuid(VMSnapshot.class, getVMSnapshotId()); + return "Creating Snapshot from Instance Snapshot : " + getResourceUuid(ApiConstants.VOLUME_ID); } @Override @@ -153,7 +153,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void create() throws ResourceAllocationException { - Snapshot snapshot = this._volumeService.allocSnapshotForVm(getVmId(), getVolumeId(), getSnapshotName()); + Snapshot snapshot = this._volumeService.allocSnapshotForVm(getVmId(), getVolumeId(), getSnapshotName(), getVMSnapshotId()); if (snapshot != null) { this.setEntityId(snapshot.getId()); this.setEntityUuid(snapshot.getUuid()); @@ -165,8 +165,8 @@ public void create() throws ResourceAllocationException { @Override public void execute() { VMSnapshot vmSnapshot = _vmSnapshotService.getVMSnapshotById(getVMSnapshotId()); - logger.info("CreateSnapshotFromVMSnapshotCmd with Instance Snapshot {} with ID: {} and Snapshot [ID: {}, UUID: {}]", vmSnapshot, getVMSnapshotId(), getEntityId(), getEntityUuid()); - CallContext.current().setEventDetails("Instance Snapshot Id: " + vmSnapshot.getUuid()); + logger.info("CreateSnapshotFromVMSnapshotCmd with {} and Snapshot [ID: {}, UUID: {}]", vmSnapshot, getEntityId(), getEntityUuid()); + CallContext.current().setEventDetails("Instance Snapshot ID: " + vmSnapshot.getUuid()); Snapshot snapshot = null; try { snapshot = _snapshotService.backupSnapshotFromVmSnapshot(getEntityId(), getVmId(), getVolumeId(), getVMSnapshotId()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java index 246a2fefa9fc..b1e7b2a00040 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/CreateSnapshotPolicyCmd.java @@ -16,11 +16,13 @@ // under the License. package org.apache.cloudstack.api.command.user.snapshot; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.PermissionDeniedException; +import com.cloud.projects.Project; +import com.cloud.storage.Volume; +import com.cloud.storage.snapshot.SnapshotPolicy; +import com.cloud.user.Account; +import java.util.ArrayList; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; @@ -30,16 +32,16 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.SnapshotPolicyResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.commons.collections.MapUtils; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.exception.PermissionDeniedException; -import com.cloud.projects.Project; -import com.cloud.storage.Volume; -import com.cloud.storage.snapshot.SnapshotPolicy; -import com.cloud.user.Account; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.apache.commons.lang3.BooleanUtils; @APICommand(name = "createSnapshotPolicy", description = "Creates a Snapshot policy for the account.", responseObject = SnapshotPolicyResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -83,6 +85,21 @@ public class CreateSnapshotPolicyCmd extends BaseCmd { "The snapshots will always be made available in the zone in which the volume is present.") protected List zoneIds; + @Parameter(name = ApiConstants.STORAGE_ID_LIST, + type=CommandType.LIST, + collectionType = CommandType.UUID, + entityType = StoragePoolResponse.class, + description = "A comma-separated list of IDs of the storage pools in other zones in which the snapshot will be made available. " + + "The snapshot will always be made available in the zone in which the volume is present.", + since = "4.21.0") + protected List storagePoolIds; + + @Parameter (name = ApiConstants.USE_STORAGE_REPLICATION, + type=CommandType.BOOLEAN, + since = "4.21.0", + description = "Enables the snapshot to be copied to the supported primary storages when the config 'use.storage.replication' is set to true for the storage or globally. " + + "This is supported only for StorPool storage for now.") + protected Boolean useStorageReplication; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -119,6 +136,14 @@ public List getZoneIds() { return zoneIds; } + public List getStoragePoolIds() { + return storagePoolIds == null ? new ArrayList<>() : storagePoolIds; + } + + public Boolean useStorageReplication() { + return BooleanUtils.toBoolean(useStorageReplication); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmd.java index 894777375819..b4eaceb61ba6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/DeleteSnapshotCmd.java @@ -85,7 +85,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting Snapshot: " + this._uuidMgr.getUuid(Snapshot.class, getId()); + return "Deleting Snapshot with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -100,7 +100,7 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class, getId())); + CallContext.current().setEventDetails("Snapshot ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _snapshotService.deleteSnapshot(getId(), getZoneId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java index 3f0f82ea4e3b..dacdd20b3969 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ExtractSnapshotCmd.java @@ -96,12 +96,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "Snapshot extraction job"; + return "Starting Snapshot extraction for Snapshot with ID: " + getResourceUuid(ApiConstants.ID); } @Override public void execute() { - CallContext.current().setEventDetails("Snapshot ID: " + this._uuidMgr.getUuid(Snapshot.class, getId())); + CallContext.current().setEventDetails("Snapshot ID: " + getResourceUuid(ApiConstants.ID)); String uploadUrl = _snapshotService.extractSnapshot(this); logger.info("Extract URL [{}] of snapshot [{}].", uploadUrl, id); if (uploadUrl != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmd.java index 06051492e859..df73a009a035 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmd.java @@ -23,7 +23,7 @@ import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseListCmd; +import org.apache.cloudstack.api.BaseListProjectAndAccountResourcesCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.SnapshotPolicyResponse; @@ -34,7 +34,7 @@ @APICommand(name = "listSnapshotPolicies", description = "Lists Snapshot policies.", responseObject = SnapshotPolicyResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) -public class ListSnapshotPoliciesCmd extends BaseListCmd { +public class ListSnapshotPoliciesCmd extends BaseListProjectAndAccountResourcesCmd { ///////////////////////////////////////////////////// @@ -69,13 +69,14 @@ public boolean isDisplay() { public Long getId() { return id; } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @Override public void execute() { - Pair, Integer> result = _snapshotService.listPoliciesforVolume(this); + Pair, Integer> result = _snapshotService.listSnapshotPolicies(this); ListResponse response = new ListResponse(); List policyResponses = new ArrayList(); for (SnapshotPolicy policy : result.first()) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java index 7cee7e71cf37..59881dfdabe2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/RevertSnapshotCmd.java @@ -74,7 +74,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "revert Snapshot: " + this._uuidMgr.getUuid(Snapshot.class, getId()); + return "Reverting Snapshot with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -89,7 +89,7 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class, getId())); + CallContext.current().setEventDetails("Snapshot ID: " + getResourceUuid(ApiConstants.ID)); Snapshot snapshot = _snapshotService.revertSnapshot(getId()); if (snapshot != null) { SnapshotResponse response = _responseGenerator.createSnapshotResponse(snapshot); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/UpdateSnapshotPolicyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/UpdateSnapshotPolicyCmd.java index ba98956644cb..84f8d0b3c390 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/UpdateSnapshotPolicyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/snapshot/UpdateSnapshotPolicyCmd.java @@ -104,7 +104,7 @@ public String getEventDescription() { @Override public void execute() { - CallContext.current().setEventDetails("SnapshotPolicy ID: " + getId()); + CallContext.current().setEventDetails("Snapshot policy ID: " + getResourceUuid(ApiConstants.ID)); SnapshotPolicy result = _snapshotService.updateSnapshotPolicy(this); if (result != null) { SnapshotPolicyResponse response = _responseGenerator.createSnapshotPolicyResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/CreateSSHKeyPairCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/CreateSSHKeyPairCmd.java index a55b20593025..1b79c11644fb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/CreateSSHKeyPairCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/CreateSSHKeyPairCmd.java @@ -77,7 +77,7 @@ public Long getProjectId() { ///////////////////////////////////////////////////// @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/RegisterSSHKeyPairCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/RegisterSSHKeyPairCmd.java index 36c708ea1112..f7af86d08357 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/RegisterSSHKeyPairCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/ssh/RegisterSSHKeyPairCmd.java @@ -85,7 +85,7 @@ public Long getProjectId() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java index b078ce4aae95..24290bc345e1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSDiskOfferingCmd.java @@ -117,7 +117,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Changing disk offering for the Shared FileSystem " + id; + return "Changing disk offering for the Shared FileSystem with ID:" + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java index 70fb543d64c3..1ac0f27067b4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ChangeSharedFSServiceOfferingCmd.java @@ -96,7 +96,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Changing service offering for the Shared FileSystem " + id; + return "Changing service offering for the Shared FileSystem with ID:" + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java index ddaa31612a89..595b611b5c0d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/CreateSharedFSCmd.java @@ -230,7 +230,7 @@ public Long getApiResourceId() { } @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java index 09fae53f1284..35f16a4dc2a0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/DestroySharedFSCmd.java @@ -95,7 +95,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Destroying Shared FileSystem " + id; + return "Destroying Shared FileSystem with ID:" + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java index 39b99218b667..8960aa3e4d40 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/ExpungeSharedFSCmd.java @@ -74,7 +74,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Expunging Shared FileSystem " + id; + return "Expunging Shared FileSystem with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java index 576c472b6eb2..75565796caa4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/RestartSharedFSCmd.java @@ -94,7 +94,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Restarting Shared FileSystem " + id; + return "Restarting Shared FileSystem with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java index bd384aceef73..d7440b532b31 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StartSharedFSCmd.java @@ -84,7 +84,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Starting Shared FileSystem " + id; + return "Starting Shared FileSystem with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java index d6e0737144a5..3800b16289e7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/storage/sharedfs/StopSharedFSCmd.java @@ -92,7 +92,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Stopping Shared FileSystem " + id; + return "Stopping Shared FileSystem with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java index 66a20fac8606..02601b2257f1 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CopyTemplateCmd.java @@ -136,19 +136,21 @@ public String getEventType() { @Override public String getEventDescription() { - StringBuilder descBuilder = new StringBuilder(); - if (getDestinationZoneIds() != null) { + String description = "Copying Template: " + getResourceUuid(ApiConstants.ID); + + if (getSourceZoneId() != null) { + description += " from zone: " + getResourceUuid(ApiConstants.SOURCE_ZONE_ID); + } + if (getDestinationZoneIds() != null) { + description += " to zones: "; for (Long destId : getDestinationZoneIds()) { - descBuilder.append(", "); - descBuilder.append(this._uuidMgr.getUuid(DataCenter.class, destId)); - } - if (descBuilder.length() > 0) { - descBuilder.deleteCharAt(0); + description += this._uuidMgr.getUuid(DataCenter.class, destId); + description += ", "; } } - return "Copying Template: " + this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId()) +((getSourceZoneId() != null) ? " from zone: " + this._uuidMgr.getUuid(DataCenter.class, getSourceZoneId()) : "") + ((descBuilder.length() > 0) ? " to zones: " + descBuilder.toString() : ""); + return description; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java index 6e6acf162521..b5e41ff449ca 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/CreateTemplateCmd.java @@ -150,7 +150,7 @@ public class CreateTemplateCmd extends BaseAsyncCreateCmd implements UserCmd { private String accountName; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template. Valid options are: x86_64, aarch64. Defaults to x86_64", + description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x. Defaults to x86_64", since = "4.20.2") private String arch; @@ -301,7 +301,7 @@ public void create() throws ResourceAllocationException { @Override public void execute() { CallContext.current().setEventDetails( - "Template Id: " + getEntityUuid() + ((getSnapshotId() == null) ? " from volume Id: " + this._uuidMgr.getUuid(Volume.class, getVolumeId()) : " from Snapshot Id: " + this._uuidMgr.getUuid(Snapshot.class, getSnapshotId()))); + "Template ID: " + getEntityUuid() + ((getSnapshotId() == null) ? " from volume with ID: " + getResourceUuid(ApiConstants.VOLUME_ID) : " from Snapshot with ID: " + getResourceUuid(ApiConstants.SNAPSHOT_ID))); VirtualMachineTemplate template = _templateService.createPrivateTemplate(this); if (template != null) { @@ -354,14 +354,12 @@ private void ensureAccessCheck(Account account) { private Long findAccountIdToUse(Account callingAccount) { Long accountIdToUse = null; try { - accountIdToUse = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + accountIdToUse = _accountService.finalizeAccountId(accountName, domainId, projectId, true); } catch (InvalidParameterValueException | PermissionDeniedException ex) { - if (logger.isDebugEnabled()) { - logger.debug(String.format("An exception occurred while finalizing account id with accountName, domainId and projectId" + - "using callingAccountId=%s", callingAccount.getUuid()), ex); - } - logger.warn("Unable to find accountId associated with accountName=" + accountName + " and domainId=" - + domainId + " or projectId=" + projectId + ", using callingAccountId=" + callingAccount.getUuid()); + logger.error("Unable to find accountId associated with accountName={} and domainId={} or projectId={}" + + ", using callingAccountId={}", accountName, domainId, projectId, callingAccount.getUuid()); + logger.debug("An exception occurred while finalizing account id with accountName, domainId and projectId" + + "using callingAccountId={}", callingAccount.getUuid(), ex); } return accountIdToUse != null ? accountIdToUse : callingAccount.getAccountId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java index fef70d188e59..3c7b1e2708b8 100755 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/DeleteTemplateCmd.java @@ -98,7 +98,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting Template " + this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId()); + return "Deleting Template with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -113,7 +113,7 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("Template Id: " + this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId())); + CallContext.current().setEventDetails("Template ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _templateService.deleteTemplate(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java index b0215b12ef2e..d3f039ce38de 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ExtractTemplateCmd.java @@ -28,7 +28,6 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; -import com.cloud.dc.DataCenter; import com.cloud.event.EventTypes; import com.cloud.exception.InternalErrorException; import com.cloud.template.VirtualMachineTemplate; @@ -101,15 +100,14 @@ public String getEventType() { @Override public String getEventDescription() { - String templateId = this._uuidMgr.getUuid(VirtualMachineTemplate.class, getId()); - String baseDescription = String.format("Extracting Template: %s", templateId); + String description = "Extracting Template with ID: " + getResourceUuid(ApiConstants.ID); Long zoneId = getZoneId(); - if (zoneId == null) { - return baseDescription; + if (zoneId != null) { + description += "from zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } - return String.format("%s from zone: %s", baseDescription, this._uuidMgr.getUuid(DataCenter.class, zoneId)); + return description; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java index 9671dd42b98e..e6e178baada6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/GetUploadParamsForTemplateCmd.java @@ -57,7 +57,7 @@ public class GetUploadParamsForTemplateCmd extends AbstractGetUploadParamsCmd { private Long osTypeId; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template. Valid options are: x86_64, aarch64", + description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; @@ -99,6 +99,16 @@ public class GetUploadParamsForTemplateCmd extends AbstractGetUploadParamsCmd { description = "(VMware only) true if Instance deployments should preserve all the configurations defined for this Template", since = "4.15.1") private Boolean deployAsIs; + @Parameter(name=ApiConstants.FOR_CKS, + type = CommandType.BOOLEAN, + description = "if true, the templates would be available for deploying CKS clusters", since = "4.21.0") + protected Boolean forCks; + + @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, + description = "the type of the template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).", + since = "4.22.0") + private String templateType; + public String getDisplayText() { return StringUtils.isBlank(displayText) ? getName() : displayText; } @@ -168,10 +178,18 @@ public boolean isDeployAsIs() { Boolean.TRUE.equals(deployAsIs); } + public boolean isForCks() { + return Boolean.TRUE.equals(forCks); + } + public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public String getTemplateType() { + return templateType; + } + @Override public void execute() throws ServerApiException { validateRequest(); @@ -205,7 +223,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(getAccountName(), getDomainId(), getProjectId(), true); + Long accountId = _accountService.finalizeAccountId(getAccountName(), getDomainId(), getProjectId(), true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatePermissionsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatePermissionsCmd.java index 9dabbec37500..7e7efcf87cf4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatePermissionsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatePermissionsCmd.java @@ -1,4 +1,4 @@ -// Licensedname = "listTemplatePermissions", to the Apache Software Foundation (ASF) under one +// 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 diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java index 9edca36147c6..0b52413aaf19 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/ListTemplatesCmd.java @@ -16,17 +16,11 @@ // under the License. package org.apache.cloudstack.api.command.user.template; -import com.cloud.cpu.CPU; -import com.cloud.exception.InvalidParameterValueException; -import com.cloud.server.ResourceIcon; -import com.cloud.server.ResourceTag; -import org.apache.cloudstack.api.response.ResourceIconResponse; -import org.apache.commons.collections.CollectionUtils; - import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; + import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -34,15 +28,21 @@ import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.ExtensionResponse; +import org.apache.cloudstack.api.response.GuestOSCategoryResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.TemplateResponse; import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.server.ResourceTag; import com.cloud.template.VirtualMachineTemplate; import com.cloud.template.VirtualMachineTemplate.TemplateFilter; import com.cloud.user.Account; -import org.apache.commons.lang3.StringUtils; @APICommand(name = "listTemplates", description = "List all public, private, and privileged Templates.", responseObject = TemplateResponse.class, entityType = {VirtualMachineTemplate.class}, responseView = ResponseView.Restricted, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) @@ -106,11 +106,29 @@ public class ListTemplatesCmd extends BaseListTaggedResourcesCmd implements User since = "4.19.0") private Boolean isVnf; + @Parameter(name = ApiConstants.FOR_CKS, type = CommandType.BOOLEAN, + description = "list templates that can be used to deploy CKS clusters", + since = "4.21.0") + private Boolean forCks; + @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template. Valid options are: x86_64, aarch64", + description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; + @Parameter(name = ApiConstants.OS_CATEGORY_ID, type = CommandType.UUID, entityType = GuestOSCategoryResponse.class, + description = "the ID of the OS category for the template", + since = "4.21.0") + private Long osCategoryId; + + @Parameter(name = ApiConstants.EXTENSION_ID, type = CommandType.UUID, entityType = ExtensionResponse.class, + description = "ID of the extension for the template", + since = "4.21.0") + private Long extensionId; + + @Parameter(name = ApiConstants.IS_READY, type = CommandType.BOOLEAN, description = "list templates that are ready to be deployed", since = "4.21.0") + private Boolean ready; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -180,6 +198,13 @@ public boolean listInReadyState() { boolean onlyReady = (templateFilter == TemplateFilter.featured) || (templateFilter == TemplateFilter.selfexecutable) || (templateFilter == TemplateFilter.sharedexecutable) || (templateFilter == TemplateFilter.executable && isAccountSpecific) || (templateFilter == TemplateFilter.community); + + if (!onlyReady) { + if (isReady() != null && isReady().booleanValue() != onlyReady) { + onlyReady = isReady().booleanValue(); + } + } + return onlyReady; } @@ -198,6 +223,8 @@ public Boolean getVnf() { return isVnf; } + public Boolean getForCks() { return forCks; } + public CPU.CPUArch getArch() { if (StringUtils.isBlank(arch)) { return null; @@ -205,6 +232,18 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Long getOsCategoryId() { + return osCategoryId; + } + + public Long getExtensionId() { + return extensionId; + } + + public Boolean isReady() { + return ready; + } + @Override public String getCommandName() { return s_name; @@ -218,24 +257,14 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { ListResponse response = _queryService.listTemplates(this); - if (response != null && response.getCount() > 0 && getShowIcon()) { - updateTemplateResponse(response.getResponses()); + if (response != null && getShowIcon()) { + _responseGenerator.updateTemplateIsoResponsesForIcons(response.getResponses(), + ResourceTag.ResourceObjectType.Template); } response.setResponseName(getCommandName()); setResponseObject(response); } - private void updateTemplateResponse(List response) { - for (TemplateResponse templateResponse : response) { - ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.Template, templateResponse.getId()); - if (resourceIcon == null) { - continue; - } - ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); - templateResponse.setResourceIconResponse(iconResponse); - } - } - public List getIds() { if (ids == null) { return Collections.emptyList(); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java index 851dbaa192f2..49992ac66611 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/RegisterTemplateCmd.java @@ -16,15 +16,12 @@ // under the License. package org.apache.cloudstack.api.command.user.template; -import com.cloud.cpu.CPU; -import com.cloud.hypervisor.Hypervisor; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; -import com.cloud.hypervisor.HypervisorGuru; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; @@ -35,6 +32,7 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ExtensionResponse; import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.ProjectResponse; @@ -43,7 +41,10 @@ import org.apache.cloudstack.context.CallContext; import org.apache.commons.lang3.StringUtils; +import com.cloud.cpu.CPU; import com.cloud.exception.ResourceAllocationException; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.hypervisor.HypervisorGuru; import com.cloud.template.VirtualMachineTemplate; @APICommand(name = "registerTemplate", description = "Registers an existing Template into the CloudStack cloud. ", responseObject = TemplateResponse.class, responseView = ResponseView.Restricted, @@ -168,16 +169,29 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd { description = "(VMware only) true if Instance deployments should preserve all the configurations defined for this Template", since = "4.15.1") protected Boolean deployAsIs; + @Parameter(name=ApiConstants.FOR_CKS, + type = CommandType.BOOLEAN, + description = "if true, the templates would be available for deploying CKS clusters", since = "4.21.0") + protected Boolean forCks; + @Parameter(name = ApiConstants.TEMPLATE_TYPE, type = CommandType.STRING, description = "the type of the template. Valid options are: USER/VNF (for all users) and SYSTEM/ROUTING/BUILTIN (for admins only).", since = "4.19.0") private String templateType; @Parameter(name = ApiConstants.ARCH, type = CommandType.STRING, - description = "the CPU arch of the template. Valid options are: x86_64, aarch64", + description = "the CPU arch of the template. Valid options are: x86_64, aarch64, s390x", since = "4.20") private String arch; + @Parameter(name = ApiConstants.EXTENSION_ID, type = CommandType.UUID, entityType = ExtensionResponse.class, + description = "ID of the extension", + since = "4.21.0") + private Long extensionId; + + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, type = CommandType.MAP, description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].endpoint.url=urlvalue", since = "4.21.0") + protected Map externalDetails; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -295,6 +309,10 @@ public boolean isDeployAsIs() { Boolean.TRUE.equals(deployAsIs); } + public boolean isForCks() { + return Boolean.TRUE.equals(forCks); + } + public String getTemplateType() { return templateType; } @@ -303,6 +321,14 @@ public CPU.CPUArch getArch() { return CPU.CPUArch.fromType(arch); } + public Long getExtensionId() { + return extensionId; + } + + public Map getExternalDetails() { + return convertExternalDetailsToMap(externalDetails); + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -318,7 +344,7 @@ public ApiCommandResourceType getInstanceType() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java index f51e5169b372..56d50285692d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/template/UpdateTemplateCmd.java @@ -46,6 +46,11 @@ public class UpdateTemplateCmd extends BaseUpdateTemplateOrIsoCmd implements Use @Parameter(name = ApiConstants.TEMPLATE_TAG, type = CommandType.STRING, description = "the tag for this template.", since = "4.20.0") private String templateTag; + @Parameter(name = ApiConstants.FOR_CKS, type = CommandType.BOOLEAN, + description = "indicates that the template can be used for deployment of CKS clusters", + since = "4.21.0") + private Boolean forCks; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -63,6 +68,10 @@ public String getTemplateTag() { return templateTag; } + public Boolean getForCks() { + return forCks; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/BaseRegisterUserDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/BaseRegisterUserDataCmd.java new file mode 100644 index 000000000000..c002bd226a0d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/BaseRegisterUserDataCmd.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.userdata; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.network.NetworkModel; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public abstract class BaseRegisterUserDataCmd extends BaseCmd { + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Name of the user data") + private String name; + + //Owner information + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the user data. Must be used with domainId.") + private String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, + type = CommandType.UUID, + entityType = DomainResponse.class, + description = "an optional domainId for the user data. If the account parameter is used, domainId must also be used.") + private Long domainId; + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "an optional project for the user data") + private Long projectId; + + @Parameter(name = ApiConstants.PARAMS, type = CommandType.STRING, description = "comma separated list of variables declared in user data content") + private String params; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getName() { + return name; + } + + public String getAccountName() { + return accountName; + } + + public Long getDomainId() { + return domainId; + } + + public Long getProjectId() { + return projectId; + } + + public String getParams() { + checkForVRMetadataFileNames(params); + return params; + } + + public void checkForVRMetadataFileNames(String params) { + if (StringUtils.isNotEmpty(params)) { + List keyValuePairs = new ArrayList<>(Arrays.asList(params.split(","))); + keyValuePairs.retainAll(NetworkModel.metadataFileNames); + if (!keyValuePairs.isEmpty()) { + throw new InvalidParameterValueException(String.format("Params passed here have a few virtual router metadata file names %s", keyValuePairs)); + } + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/DeleteCniConfigurationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/DeleteCniConfigurationCmd.java new file mode 100644 index 000000000000..8faa612d3d97 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/DeleteCniConfigurationCmd.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.userdata; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.user.Account; +import com.cloud.user.UserData; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + + +@APICommand(name = "deleteCniConfiguration", description = "Deletes a CNI Configuration", responseObject = SuccessResponse.class, entityType = {UserData.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class DeleteCniConfigurationCmd extends DeleteUserDataCmd { + + public static final Logger logger = LogManager.getLogger(DeleteCniConfigurationCmd.class.getName()); + + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + boolean result = _mgr.deleteCniConfiguration(this); + if (result) { + SuccessResponse response = new SuccessResponse(getCommandName()); + response.setSuccess(result); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete CNI configuration"); + } + } + + @Override + public long getEntityOwnerId() { + Account account = CallContext.current().getCallingAccount(); + Long domainId = this.getDomainId(); + String accountName = this.getAccountName(); + if ((account == null || _accountService.isAdmin(account.getId())) && (domainId != null && accountName != null)) { + Account userAccount = _responseGenerator.findAccountByNameDomain(accountName, domainId); + if (userAccount != null) { + return userAccount.getId(); + } + } + + if (account != null) { + return account.getId(); + } + + return Account.ACCOUNT_ID_SYSTEM; // no account info given, parent this command to SYSTEM so ERROR events are tracked + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListCniConfigurationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListCniConfigurationCmd.java new file mode 100644 index 000000000000..3a0c3f1168bc --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListCniConfigurationCmd.java @@ -0,0 +1,59 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.userdata; + +import com.cloud.user.UserData; +import com.cloud.utils.Pair; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.UserDataResponse; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.ArrayList; +import java.util.List; + +@APICommand(name = "listCniConfiguration", description = "List user data for CNI plugins", responseObject = UserDataResponse.class, entityType = {UserData.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.21.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class ListCniConfigurationCmd extends ListUserDataCmd { + public static final Logger logger = LogManager.getLogger(ListCniConfigurationCmd.class.getName()); + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + Pair, Integer> resultList = _mgr.listUserDatas(this, true); + List responses = new ArrayList<>(); + for (UserData result : resultList.first()) { + UserDataResponse r = _responseGenerator.createUserDataResponse(result); + r.setObjectName(ApiConstants.CNI_CONFIG); + responses.add(r); + } + + ListResponse response = new ListResponse<>(); + response.setResponses(responses, resultList.second()); + response.setResponseName(getCommandName()); + setResponseObject(response); + } + + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmd.java index c2da0e25e204..693342305242 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmd.java @@ -61,7 +61,7 @@ public String getName() { @Override public void execute() { - Pair, Integer> resultList = _mgr.listUserDatas(this); + Pair, Integer> resultList = _mgr.listUserDatas(this, false); List responses = new ArrayList<>(); for (UserData result : resultList.first()) { UserDataResponse r = _responseGenerator.createUserDataResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterCniConfigurationCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterCniConfigurationCmd.java new file mode 100644 index 000000000000..3f1de41eab89 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterCniConfigurationCmd.java @@ -0,0 +1,77 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.userdata; + +import com.cloud.user.UserData; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.api.response.UserDataResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@APICommand(name = "registerCniConfiguration", + description = "Register a CNI Configuration to be used with CKS cluster", + since = "4.21.0", + responseObject = SuccessResponse.class, + requestHasSensitiveInfo = false, + responseHasSensitiveInfo = false, + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class RegisterCniConfigurationCmd extends BaseRegisterUserDataCmd { + public static final Logger logger = LogManager.getLogger(RegisterCniConfigurationCmd.class.getName()); + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.CNI_CONFIG, type = CommandType.STRING, description = "CNI Configuration content to be registered as User data", length = 1048576) + private String cniConfig; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public String getCniConfig() { + return cniConfig; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + @Override + public void execute() { + UserData result = _mgr.registerCniConfiguration(this); + UserDataResponse response = _responseGenerator.createUserDataResponse(result); + response.setResponseName(getCommandName()); + response.setObjectName(ApiConstants.CNI_CONFIG); + setResponseObject(response); + } + + @Override + public long getEntityOwnerId() { + Long accountId = _accountService.finalizeAccountId(getAccountName(), getDomainId(), getProjectId(), true); + if (accountId == null) { + return CallContext.current().getCallingAccount().getId(); + } + + return accountId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmd.java index 4d455f913c51..d99f2fd066d6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmd.java @@ -16,30 +16,20 @@ // under the License. package org.apache.cloudstack.api.command.user.userdata; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserDataResponse; import org.apache.cloudstack.context.CallContext; -import org.apache.commons.lang3.StringUtils; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.NetworkRuleConflictException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.network.NetworkModel; import com.cloud.user.UserData; @APICommand(name = "registerUserData", @@ -49,29 +39,13 @@ requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) -public class RegisterUserDataCmd extends BaseCmd { +public class RegisterUserDataCmd extends BaseRegisterUserDataCmd { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, required = true, description = "Name of the User Data") - private String name; - - //Owner information - @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "An optional Account for the User Data. Must be used with domainId.") - private String accountName; - - @Parameter(name = ApiConstants.DOMAIN_ID, - type = CommandType.UUID, - entityType = DomainResponse.class, - description = "An optional domainId for the User Data. If the Account parameter is used, domainId must also be used.") - private Long domainId; - - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "An optional project for the User Data") - private Long projectId; - @Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, required = true, @@ -80,58 +54,23 @@ public class RegisterUserDataCmd extends BaseCmd { "Using HTTP POST (via POST body), you can send up to 32KB of data after base64 encoding, " + "which can be increased upto 1MB using the vm.userdata.max.length setting", length = 1048576) - private String userData; - - @Parameter(name = ApiConstants.PARAMS, type = CommandType.STRING, description = "Comma separated list of variables declared in the User Data content") - private String params; - + protected String userData; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public String getName() { - return name; - } - - public String getAccountName() { - return accountName; - } - - public Long getDomainId() { - return domainId; - } - - public Long getProjectId() { - return projectId; - } - public String getUserData() { return userData; } - public String getParams() { - checkForVRMetadataFileNames(params); - return params; - } - - public void checkForVRMetadataFileNames(String params) { - if (StringUtils.isNotEmpty(params)) { - List keyValuePairs = new ArrayList<>(Arrays.asList(params.split(","))); - keyValuePairs.retainAll(NetworkModel.metadataFileNames); - if (!keyValuePairs.isEmpty()) { - throw new InvalidParameterValueException(String.format("Params passed here have a few virtual router metadata file names %s", keyValuePairs)); - } - } - } - ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(getAccountName(), getDomainId(), getProjectId(), true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java index 3882b157e6e8..9d96f104885e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddIpToVmNicCmd.java @@ -56,6 +56,9 @@ public class AddIpToVmNicCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, required = false, description = "Secondary IP Address") private String ipAddr; + @Parameter(name = ApiConstants.DESCRIPTION, type = CommandType.STRING, required = false, description = "Description of the secondary IP address", length = 2048) + private String description; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -89,7 +92,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Associating IP to NIC id=" + this._uuidMgr.getUuid(Nic.class, getNicId()) + " belonging to Network id=" + this._uuidMgr.getUuid(Network.class, getNetworkId()); + return "Associating secondary IP address to NIC with ID: " + getResourceUuid(ApiConstants.NIC_ID) + " belonging to Network with ID: " + this._uuidMgr.getUuid(Network.class, getNetworkId()); } ///////////////////////////////////////////////////// @@ -108,11 +111,11 @@ public static String getResultObjectName() { @Override public void execute() throws ResourceUnavailableException, ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { - CallContext.current().setEventDetails("Nic Id: " + this._uuidMgr.getUuid(Nic.class, getNicId())); + CallContext.current().setEventDetails("Nic ID: " + getResourceUuid(ApiConstants.NIC_ID)); NicSecondaryIp result = _entityMgr.findById(NicSecondaryIp.class, getEntityId()); if (result != null) { - CallContext.current().setEventDetails("secondary Ip Id: " + getEntityUuid()); + CallContext.current().setEventDetails("Secondary IP address ID: " + getEntityUuid()); boolean success = false; success = _networkService.configureNicSecondaryIp(result, isZoneSGEnabled()); @@ -160,7 +163,7 @@ public void create() throws ResourceAllocationException { } try { - result = _networkService.allocateSecondaryGuestIP(getNicId(), requestedIpPair); + result = _networkService.allocateSecondaryGuestIP(getNicId(), requestedIpPair, description); if (result != null) { setEntityId(result.getId()); setEntityUuid(result.getUuid()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java index cf91e15601ba..f6ef955956f6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/AddNicToVMCmd.java @@ -40,7 +40,6 @@ import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; -import com.cloud.network.Network; import com.cloud.user.Account; import com.cloud.uservm.UserVm; import com.cloud.utils.net.Dhcp; @@ -101,6 +100,26 @@ public String getMacAddress() { return NetUtils.standardizeMacAddress(macaddr); } + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public void setNetworkId(Long netId) { + this.netId = netId; + } + + public void setIpaddr(String ipaddr) { + this.ipaddr = ipaddr; + } + + public void setMacAddress(String macaddr) { + this.macaddr = macaddr; + } + + public void setDhcpOptions(Map dhcpOptions) { + this.dhcpOptions = dhcpOptions; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -121,7 +140,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Adding Network " + this._uuidMgr.getUuid(Network.class, getNetworkId()) + " to User Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()); + return "Adding NIC on Network " + getResourceUuid(ApiConstants.NETWORK_ID) + " to User Instance: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override @@ -167,7 +186,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()) + " Network Id: " + this._uuidMgr.getUuid(Network.class, getNetworkId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " Network ID: " + getResourceUuid(ApiConstants.NETWORK_ID)); UserVm result = _userVmService.addNicToVirtualMachine(this); ArrayList dc = new ArrayList(); dc.add(VMDetails.valueOf("nics")); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java new file mode 100644 index 000000000000..38e9921853a3 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/BaseDeployVMCmd.java @@ -0,0 +1,877 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.vm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import javax.annotation.Nonnull; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.affinity.AffinityGroupResponse; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.ApiArgValidator; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; +import org.apache.cloudstack.api.BaseAsyncCreateCustomIdCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.command.user.UserCmd; +import org.apache.cloudstack.api.response.DiskOfferingResponse; +import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.api.response.ProjectResponse; +import org.apache.cloudstack.api.response.SecurityGroupResponse; +import org.apache.cloudstack.api.response.UserDataResponse; +import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.kms.KMSKey; +import org.apache.cloudstack.vm.lease.VMLeaseManager; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections.MapUtils; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + +import com.cloud.agent.api.LogLevel; +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.Network; +import com.cloud.network.Network.IpAddresses; +import com.cloud.offering.DiskOffering; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.utils.net.Dhcp; +import com.cloud.utils.net.NetUtils; +import com.cloud.vm.VmDetailConstants; +import com.cloud.vm.VmDiskInfo; + +public abstract class BaseDeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityGroupAction, UserCmd { + + private static final String s_name = "deployvirtualmachineresponse"; + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "availability zone for the virtual machine") + protected Long zoneId; + + @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "host name for the virtual machine", validations = {ApiArgValidator.RFCComplianceDomainName}) + protected String name; + + @Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "an optional user generated name for the virtual machine") + protected String displayName; + + @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, description="The password of the virtual machine. If null, a random password will be generated for the VM.", + since="4.19.0.0") + protected String password; + + //Owner information + @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "an optional account for the virtual machine. Must be used with domainId.") + protected String accountName; + + @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "an optional domainId for the virtual machine. If the account parameter is used, domainId must also be used. If account is NOT provided then virtual machine will be assigned to the caller account and domain.") + protected Long domainId; + + //Network information + //@ACL(accessType = AccessType.UseEntry) + @Parameter(name = ApiConstants.NETWORK_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = NetworkResponse.class, description = "list of network ids used by virtual machine. Can't be specified with ipToNetworkList parameter") + protected List networkIds; + + @Parameter(name = ApiConstants.BOOT_TYPE, type = CommandType.STRING, required = false, description = "Guest VM Boot option either custom[UEFI] or default boot [BIOS]. Not applicable with VMware if the template is marked as deploy-as-is, as we honour what is defined in the template.", since = "4.14.0.0") + protected String bootType; + + @Parameter(name = ApiConstants.BOOT_MODE, type = CommandType.STRING, required = false, description = "Boot Mode [Legacy] or [Secure] Applicable when Boot Type Selected is UEFI, otherwise Legacy only for BIOS. Not applicable with VMware if the template is marked as deploy-as-is, as we honour what is defined in the template.", since = "4.14.0.0") + protected String bootMode; + + @Parameter(name = ApiConstants.BOOT_INTO_SETUP, type = CommandType.BOOLEAN, required = false, description = "Boot into hardware setup or not (ignored if startVm = false, only valid for vmware)", since = "4.15.0.0") + private Boolean bootIntoSetup; + + //DataDisk information + @ACL + @Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "the ID of the disk offering for the virtual machine. If the template is of ISO format," + + " the diskOfferingId is for the root disk volume. Otherwise this parameter is used to indicate the " + + "offering for the data disk volume. If the templateId parameter passed is from a Template object," + + " the diskOfferingId refers to a DATA Disk Volume created. If the templateId parameter passed is " + + "from an ISO object, the diskOfferingId refers to a ROOT Disk Volume created.") + private Long diskOfferingId; + + @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, description = "the arbitrary size for the DATADISK volume. Mutually exclusive with diskOfferingId") + private Long size; + + @Parameter(name = ApiConstants.ROOT_DISK_SIZE, + type = CommandType.LONG, + description = "Optional field to resize root disk on deploy. Value is in GB. Only applies to template-based deployments. Analogous to details[0].rootdisksize, which takes precedence over this parameter if both are provided", + since = "4.4") + private Long rootdisksize; + + @ACL + @Parameter(name = ApiConstants.ROOT_DISK_KMS_KEY_ID, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "ID of the KMS Key to use for root disk encryption", + since = "4.23.0") + private Long rootDiskKmsKeyId; + + @Parameter(name = ApiConstants.DATADISKS_DETAILS, + type = CommandType.MAP, + since = "4.21.0", + description = "Disk offering details for creating multiple data volumes. Mutually exclusive with diskOfferingId." + + " Example: datadisksdetails[0].diskofferingid=a2a73a84-19db-4852-8930-dfddef053341&datadisksdetails[0].size=10&datadisksdetails[0].miniops=100&datadisksdetails[0].maxiops=200&datadisksdetails[0].kmskeyid=") + private Map dataDisksDetails; + + @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "an optional group for the virtual machine") + private String group; + + @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, description = "the hypervisor on which to deploy the virtual machine. " + + "The parameter is required and respected only when hypervisor info is not set on the ISO/Template passed to the call") + protected String hypervisor; + + @Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, + description = "an optional binary data that can be sent to the virtual machine upon a successful deployment. " + + "This binary data must be base64 encoded before adding it to the request. " + + "Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " + + "Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " + + "You also need to change vm.userdata.max.length value", + length = 1048576) + protected String userData; + + @Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, description = "the ID of the Userdata", since = "4.18") + protected Long userdataId; + + @Parameter(name = ApiConstants.USER_DATA_DETAILS, type = CommandType.MAP, description = "used to specify the parameters values for the variables in userdata.", since = "4.18") + private Map userdataDetails; + + @Deprecated + @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, description = "name of the ssh key pair used to login to the virtual machine") + private String sshKeyPairName; + + @Parameter(name = ApiConstants.SSH_KEYPAIRS, type = CommandType.LIST, collectionType = CommandType.STRING, since="4.17", description = "names of the ssh key pairs used to login to the virtual machine") + protected List sshKeyPairNames; + + @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "destination Host ID to deploy the VM to - parameter available for root admin only") + private Long hostId; + + @ACL + @Parameter(name = ApiConstants.SECURITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = SecurityGroupResponse.class, description = "comma separated list of security groups id that going to be applied to the virtual machine. " + + "Should be passed only when vm is created from a zone with Basic Network support." + " Mutually exclusive with securitygroupnames parameter") + protected List securityGroupIdList; + + @ACL + @Parameter(name = ApiConstants.SECURITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = SecurityGroupResponse.class, description = "comma separated list of security groups names that going to be applied to the virtual machine." + + " Should be passed only when vm is created from a zone with Basic Network support. " + "Mutually exclusive with securitygroupids parameter") + private List securityGroupNameList; + + @Parameter(name = ApiConstants.IP_NETWORK_LIST, type = CommandType.MAP, description = "ip to network mapping. Can't be specified with networkIds parameter." + + " Example: iptonetworklist[0].ip=10.10.10.11&iptonetworklist[0].ipv6=fc00:1234:5678::abcd&iptonetworklist[0].networkid=uuid&iptonetworklist[0].mac=aa:bb:cc:dd:ee::ff - requests to use ip 10.10.10.11 in network id=uuid") + private Map ipToNetworkList; + + @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, description = "the ip address for default vm's network") + private String ipAddress; + + @Parameter(name = ApiConstants.IP6_ADDRESS, type = CommandType.STRING, description = "the ipv6 address for default vm's network") + private String ip6Address; + + @Parameter(name = ApiConstants.MAC_ADDRESS, type = CommandType.STRING, description = "the mac address for default vm's network") + private String macAddress; + + @Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, description = "an optional keyboard device type for the virtual machine. valid value can be one of de,de-ch,es,es-latam,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us") + protected String keyboard; + + @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Deploy vm for the project") + protected Long projectId; + + @Parameter(name = ApiConstants.START_VM, type = CommandType.BOOLEAN, description = "true if start vm after creating; defaulted to true if not specified") + private Boolean startVm; + + @ACL + @Parameter(name = ApiConstants.AFFINITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups id that are going to be applied to the virtual machine." + + " Mutually exclusive with affinitygroupnames parameter") + protected List affinityGroupIdList; + + @ACL + @Parameter(name = ApiConstants.AFFINITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = AffinityGroupResponse.class, description = "comma separated list of affinity groups names that are going to be applied to the virtual machine." + + "Mutually exclusive with affinitygroupids parameter") + private List affinityGroupNameList; + + @Parameter(name = ApiConstants.DISPLAY_VM, type = CommandType.BOOLEAN, since = "4.2", description = "an optional field, whether to the display the vm to the end user or not.", authorized = {RoleType.Admin}) + protected Boolean displayVm; + + @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.3", description = "used to specify the custom parameters. 'extraconfig' is not allowed to be passed in details") + protected Map details; + + @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for vm allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin }) + private String deploymentPlanner; + + @Parameter(name = ApiConstants.DHCP_OPTIONS_NETWORK_LIST, type = CommandType.MAP, description = "DHCP options which are passed to the VM on start up" + + " Example: dhcpoptionsnetworklist[0].dhcp:114=url&dhcpoptionsetworklist[0].networkid=networkid&dhcpoptionsetworklist[0].dhcp:66=www.test.com") + private Map dhcpOptionsNetworkList; + + @Parameter(name = ApiConstants.DATADISK_OFFERING_LIST, type = CommandType.MAP, since = "4.11", description = "datadisk template to disk-offering mapping;" + + " an optional parameter used to create additional data disks from datadisk templates; can't be specified with diskOfferingId parameter") + private Map dataDiskTemplateToDiskOfferingList; + + @Parameter(name = ApiConstants.EXTRA_CONFIG, type = CommandType.STRING, since = "4.12", description = "an optional URL encoded string that can be passed to the virtual machine upon successful deployment", length = 5120) + protected String extraConfig; + + @Parameter(name = ApiConstants.COPY_IMAGE_TAGS, type = CommandType.BOOLEAN, since = "4.13", description = "if true the image tags (if any) will be copied to the VM, default value is false") + private Boolean copyImageTags; + + @Parameter(name = ApiConstants.PROPERTIES, type = CommandType.MAP, since = "4.15", + description = "used to specify the vApp properties.") + @LogLevel(LogLevel.Log4jLevel.Off) + private Map vAppProperties; + + @Parameter(name = ApiConstants.NIC_NETWORK_LIST, type = CommandType.MAP, since = "4.15", + description = "VMware only: used to specify network mapping of a vApp VMware template registered \"as-is\"." + + " Example nicnetworklist[0].ip=Nic-101&nicnetworklist[0].network=uuid") + @LogLevel(LogLevel.Log4jLevel.Off) + private Map vAppNetworks; + + @Parameter(name = ApiConstants.DYNAMIC_SCALING_ENABLED, type = CommandType.BOOLEAN, since = "4.16", + description = "true if virtual machine needs to be dynamically scalable") + protected Boolean dynamicScalingEnabled; + + @Parameter(name = ApiConstants.OVERRIDE_DISK_OFFERING_ID, type = CommandType.UUID, since = "4.17", entityType = DiskOfferingResponse.class, description = "the ID of the disk offering for the virtual machine to be used for root volume instead of the disk offering mapped in service offering." + + "In case of virtual machine deploying from ISO, then the diskofferingid specified for root volume is ignored and uses this override disk offering id") + private Long overrideDiskOfferingId; + + @Parameter(name = ApiConstants.IOTHREADS_ENABLED, type = CommandType.BOOLEAN, required = false, + description = "IOThreads are dedicated event loop threads for supported disk devices to perform block I/O requests in order to improve scalability especially on an SMP host/guest with many LUNs.") + private Boolean iothreadsEnabled; + + @Parameter(name = ApiConstants.IO_DRIVER_POLICY, type = CommandType.STRING, description = "Controls specific policies on IO") + private String ioDriverPolicy; + + @Parameter(name = ApiConstants.NIC_MULTIQUEUE_NUMBER, type = CommandType.INTEGER, since = "4.18", + description = "The number of queues for multiqueue NICs.") + private Integer nicMultiqueueNumber; + + @Parameter(name = ApiConstants.NIC_PACKED_VIRTQUEUES_ENABLED, type = CommandType.BOOLEAN, since = "4.18", + description = "Enable packed virtqueues or not.") + private Boolean nicPackedVirtQueues; + + @Parameter(name = ApiConstants.INSTANCE_LEASE_DURATION, type = CommandType.INTEGER, since = "4.21.0", + description = "Number of days instance is leased for.") + private Integer leaseDuration; + + @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0", + description = "Lease expiry action, valid values are STOP and DESTROY") + private String leaseExpiryAction; + + @Parameter(name = ApiConstants.EXTERNAL_DETAILS, + type = CommandType.MAP, + description = "Details in key/value pairs using format externaldetails[i].keyname=keyvalue. Example: externaldetails[0].server.type=typevalue", + since = "4.21.0") + protected Map externalDetails; + + private List dataDiskInfoList; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + @Override + public String getCommandName() { + return s_name; + } + + public String getAccountName() { + if (accountName == null) { + return CallContext.current().getCallingAccount().getAccountName(); + } + return accountName; + } + + public Long getDiskOfferingId() { + return diskOfferingId; + } + + public Long getRootDiskKmsKeyId() { + return rootDiskKmsKeyId; + } + + public String getDeploymentPlanner() { + return deploymentPlanner; + } + + public String getDisplayName() { + return displayName; + } + + public Long getDomainId() { + if (domainId == null) { + return CallContext.current().getCallingAccount().getDomainId(); + } + return domainId; + } + + public ApiConstants.BootType getBootType() { + if (StringUtils.isNotBlank(bootType)) { + try { + String type = bootType.trim().toUpperCase(); + return ApiConstants.BootType.valueOf(type); + } catch (IllegalArgumentException e) { + String errMesg = "Invalid bootType " + bootType + "Specified for vm " + getName() + + " Valid values are: " + Arrays.toString(ApiConstants.BootType.values()); + logger.warn(errMesg); + throw new InvalidParameterValueException(errMesg); + } + } + return null; + } + + public Map getDetails() { + Map customparameterMap = convertDetailsToMap(details); + + if (getBootType() != null) { + customparameterMap.put(getBootType().toString(), getBootMode().toString()); + } + + if (rootdisksize != null && !customparameterMap.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { + customparameterMap.put(VmDetailConstants.ROOT_DISK_SIZE, rootdisksize.toString()); + } + + IoDriverPolicy ioPolicy = getIoDriverPolicy(); + if (ioPolicy != null) { + customparameterMap.put(VmDetailConstants.IO_POLICY, ioPolicy.toString()); + } + + if (BooleanUtils.toBoolean(iothreadsEnabled)) { + customparameterMap.put(VmDetailConstants.IOTHREADS, BooleanUtils.toStringTrueFalse(iothreadsEnabled)); + } + + if (nicMultiqueueNumber != null) { + customparameterMap.put(VmDetailConstants.NIC_MULTIQUEUE_NUMBER, nicMultiqueueNumber.toString()); + } + + if (BooleanUtils.toBoolean(nicPackedVirtQueues)) { + customparameterMap.put(VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, BooleanUtils.toStringTrueFalse(nicPackedVirtQueues)); + } + + if (MapUtils.isNotEmpty(externalDetails)) { + customparameterMap.putAll(getExternalDetails()); + } + return customparameterMap; + } + + public Map getExternalDetails() { + return convertExternalDetailsToMap(externalDetails); + } + + public ApiConstants.BootMode getBootMode() { + if (StringUtils.isNotBlank(bootMode)) { + try { + String mode = bootMode.trim().toUpperCase(); + return ApiConstants.BootMode.valueOf(mode); + } catch (IllegalArgumentException e) { + String msg = String.format("Invalid %s: %s specified for VM: %s. Valid values are: %s", + ApiConstants.BOOT_MODE, bootMode, getName(), Arrays.toString(ApiConstants.BootMode.values())); + logger.error(msg); + throw new InvalidParameterValueException(msg); + } + } + if (ApiConstants.BootType.UEFI.equals(getBootType())) { + String msg = String.format("%s must be specified for the VM with boot type: %s. Valid values are: %s", + ApiConstants.BOOT_MODE, getBootType(), Arrays.toString(ApiConstants.BootMode.values())); + logger.error(msg); + throw new InvalidParameterValueException(msg); + } + return null; + } + + public Map getVmProperties() { + Map map = new HashMap<>(); + if (MapUtils.isNotEmpty(vAppProperties)) { + Collection parameterCollection = vAppProperties.values(); + Iterator iterator = parameterCollection.iterator(); + while (iterator.hasNext()) { + HashMap entry = (HashMap)iterator.next(); + map.put(entry.get("key"), entry.get("value")); + } + } + return map; + } + + public Map getVmNetworkMap() { + Map map = new HashMap<>(); + if (MapUtils.isNotEmpty(vAppNetworks)) { + Collection parameterCollection = vAppNetworks.values(); + Iterator iterator = parameterCollection.iterator(); + while (iterator.hasNext()) { + HashMap entry = (HashMap) iterator.next(); + Integer nic; + try { + nic = Integer.valueOf(entry.get(VmDetailConstants.NIC)); + } catch (NumberFormatException nfe) { + nic = null; + } + String networkUuid = entry.get(VmDetailConstants.NETWORK); + logger.trace("Checking if NIC '{}' can be mapped on network '{}'", nic, networkUuid); + if (nic == null || StringUtils.isEmpty(networkUuid) || _entityMgr.findByUuid(Network.class, networkUuid) == null) { + throw new InvalidParameterValueException(String.format("Network ID: %s for NIC ID: %s is invalid", networkUuid, nic)); + } + map.put(nic, _entityMgr.findByUuid(Network.class, networkUuid).getId()); + } + } + return map; + } + + public String getGroup() { + return group; + } + + public HypervisorType getHypervisor() { + return HypervisorType.getType(hypervisor); + } + + public Boolean isDisplayVm() { + return displayVm; + } + + @Override + public boolean isDisplay() { + if(displayVm == null) + return true; + else + return displayVm; + } + + public List getSecurityGroupNameList() { + return securityGroupNameList; + } + + public List getSecurityGroupIdList() { + return securityGroupIdList; + } + + public Long getSize() { + return size; + } + + public String getUserData() { + return userData; + } + + public Long getUserdataId() { + return userdataId; + } + + public Map getUserdataDetails() { + return convertDetailsToMap(userdataDetails); + } + + public Long getZoneId() { + return zoneId; + } + + public String getPassword() { + return password; + } + + public Integer getLeaseDuration() { + return leaseDuration; + } + + public VMLeaseManager.ExpiryAction getLeaseExpiryAction() { + if (StringUtils.isBlank(leaseExpiryAction)) { + return null; + } + VMLeaseManager.ExpiryAction action = EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, leaseExpiryAction); + if (action == null) { + throw new InvalidParameterValueException("Invalid value configured for leaseexpiryaction, valid values are: " + + com.cloud.utils.EnumUtils.listValues(VMLeaseManager.ExpiryAction.values())); + } + return action; + } + + public List getNetworkIds() { + if (MapUtils.isNotEmpty(vAppNetworks)) { + if (CollectionUtils.isNotEmpty(networkIds) || ipAddress != null || getIp6Address() != null || MapUtils.isNotEmpty(ipToNetworkList)) { + throw new InvalidParameterValueException(String.format("%s can't be specified along with %s, %s, %s", ApiConstants.NIC_NETWORK_LIST, ApiConstants.NETWORK_IDS, ApiConstants.IP_ADDRESS, ApiConstants.IP_NETWORK_LIST)); + } else { + return new ArrayList<>(); + } + } + if (ipToNetworkList != null && !ipToNetworkList.isEmpty()) { + if ((networkIds != null && !networkIds.isEmpty()) || ipAddress != null || getIp6Address() != null) { + throw new InvalidParameterValueException("ipToNetworkMap can't be specified along with networkIds or ipAddress"); + } else { + List networks = new ArrayList(); + networks.addAll(getIpToNetworkMap().keySet()); + return networks; + } + } + return networkIds; + } + + public String getName() { + return name; + } + + public List getSSHKeyPairNames() { + List sshKeyPairs = new ArrayList(); + if(sshKeyPairNames != null) { + sshKeyPairs = sshKeyPairNames; + } + if(sshKeyPairName != null && !sshKeyPairName.isEmpty()) { + sshKeyPairs.add(sshKeyPairName); + } + return sshKeyPairs; + } + + public List getDataDiskInfoList() { + if (this.dataDiskInfoList != null) { + return this.dataDiskInfoList; + } + if (dataDisksDetails == null || dataDisksDetails.isEmpty()) { + return null; + } + if (dataDiskTemplateToDiskOfferingList != null) { + throw new InvalidParameterValueException("datadisktemplatetodiskofferinglist parameter can't be specified along with datadisksdetails parameter"); + } + List vmDiskInfoList = new ArrayList<>(); + Collection dataDisksCollection = dataDisksDetails.values(); + Iterator iter = dataDisksCollection.iterator(); + while (iter.hasNext()) { + HashMap dataDisk = (HashMap)iter.next(); + String diskOfferingUuid = dataDisk.get(ApiConstants.DISK_OFFERING_ID); + if (diskOfferingUuid == null) { + throw new InvalidParameterValueException("diskofferingid parameter is required for datadiskdetails"); + } + DiskOffering diskOffering = _entityMgr.findByUuid(DiskOffering.class, diskOfferingUuid); + if (diskOffering == null) { + throw new InvalidParameterValueException("Unable to find disk offering " + diskOfferingUuid); + } + if (diskOffering.isComputeOnly()) { + throw new InvalidParameterValueException(String.format("The disk offering id %d provided is directly mapped to a service offering, please provide an individual disk offering", diskOffering.getUuid())); + } + + Long size = null; + Long minIops = null; + Long maxIops = null; + if (dataDisk.get(ApiConstants.DEVICE_ID) == null) { + throw new InvalidParameterValueException("deviceid parameter is required for datadiskdetails"); + } + Long deviceId = Long.parseLong(dataDisk.get(ApiConstants.DEVICE_ID)); + if (diskOffering.isCustomized()) { + if (dataDisk.get(ApiConstants.SIZE) == null) { + throw new InvalidParameterValueException("Size is required for custom disk offering"); + } + size = Long.parseLong(dataDisk.get(ApiConstants.SIZE)); + } else { + size = diskOffering.getDiskSize() / (1024 * 1024 * 1024); + } + if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { + if (dataDisk.get(ApiConstants.MIN_IOPS) == null) { + throw new InvalidParameterValueException("Min IOPS is required for custom disk offering"); + } + if (dataDisk.get(ApiConstants.MAX_IOPS) == null) { + throw new InvalidParameterValueException("Max IOPS is required for custom disk offering"); + } + minIops = Long.parseLong(dataDisk.get(ApiConstants.MIN_IOPS)); + maxIops = Long.parseLong(dataDisk.get(ApiConstants.MAX_IOPS)); + } + + // Extract KMS key ID if provided + Long kmsKeyId = null; + String kmsKeyUuid = dataDisk.get(ApiConstants.KMS_KEY_ID); + if (kmsKeyUuid != null) { + KMSKey kmsKey = _entityMgr.findByUuid(org.apache.cloudstack.kms.KMSKey.class, kmsKeyUuid); + if (kmsKey == null) { + throw new InvalidParameterValueException("Unable to find KMS key " + kmsKeyUuid); + } + kmsKeyId = kmsKey.getId(); + } + + VmDiskInfo vmDiskInfo = new VmDiskInfo(diskOffering, size, minIops, maxIops, deviceId, kmsKeyId); + vmDiskInfoList.add(vmDiskInfo); + } + this.dataDiskInfoList = vmDiskInfoList; + return dataDiskInfoList; + } + + public Long getHostId() { + return hostId; + } + + public boolean getStartVm() { + return startVm == null ? true : startVm; + } + + public Map getIpToNetworkMap() { + if ((networkIds != null || ipAddress != null || getIp6Address() != null) && ipToNetworkList != null) { + throw new InvalidParameterValueException("NetworkIds and ipAddress can't be specified along with ipToNetworkMap parameter"); + } + LinkedHashMap ipToNetworkMap = null; + if (ipToNetworkList != null && !ipToNetworkList.isEmpty()) { + ipToNetworkMap = new LinkedHashMap(); + Collection ipsCollection = ipToNetworkList.values(); + Iterator iter = ipsCollection.iterator(); + while (iter.hasNext()) { + HashMap ips = (HashMap)iter.next(); + Long networkId = getNetworkIdFomIpMap(ips); + IpAddresses addrs = getIpAddressesFromIpMap(ips); + ipToNetworkMap.put(networkId, addrs); + } + } + + return ipToNetworkMap; + } + + @Nonnull + private IpAddresses getIpAddressesFromIpMap(HashMap ips) { + String requestedIp = ips.get("ip"); + String requestedIpv6 = ips.get("ipv6"); + String requestedMac = ips.get("mac"); + if (requestedIpv6 != null) { + requestedIpv6 = NetUtils.standardizeIp6Address(requestedIpv6); + } + if (requestedMac != null) { + if(!NetUtils.isValidMac(requestedMac)) { + throw new InvalidParameterValueException("Mac address is not valid: " + requestedMac); + } else if(!NetUtils.isUnicastMac(requestedMac)) { + throw new InvalidParameterValueException("Mac address is not unicast: " + requestedMac); + } + requestedMac = NetUtils.standardizeMacAddress(requestedMac); + } + return new IpAddresses(requestedIp, requestedIpv6, requestedMac); + } + + @Nonnull + private Long getNetworkIdFomIpMap(HashMap ips) { + Long networkId; + final String networkid = ips.get("networkid"); + Network network = _networkService.getNetwork(networkid); + if (network != null) { + networkId = network.getId(); + } else { + try { + networkId = Long.parseLong(networkid); + } catch (NumberFormatException e) { + throw new InvalidParameterValueException("Unable to translate and find entity with networkId: " + networkid); + } + } + return networkId; + } + + public String getIpAddress() { + return ipAddress; + } + + public String getIp6Address() { + if (ip6Address == null) { + return null; + } + return NetUtils.standardizeIp6Address(ip6Address); + } + + + public String getMacAddress() { + if (macAddress == null) { + return null; + } + if(!NetUtils.isValidMac(macAddress)) { + throw new InvalidParameterValueException("Mac address is not valid: " + macAddress); + } else if(!NetUtils.isUnicastMac(macAddress)) { + throw new InvalidParameterValueException("Mac address is not unicast: " + macAddress); + } + return NetUtils.standardizeMacAddress(macAddress); + } + + public List getAffinityGroupIdList() { + if (affinityGroupNameList != null && affinityGroupIdList != null) { + throw new InvalidParameterValueException("affinitygroupids parameter is mutually exclusive with affinitygroupnames parameter"); + } + + // transform group names to ids here + if (affinityGroupNameList != null) { + List affinityGroupIds = new ArrayList(); + for (String groupName : affinityGroupNameList) { + Long groupId = _responseGenerator.getAffinityGroupId(groupName, getEntityOwnerId()); + if (groupId == null) { + throw new InvalidParameterValueException("Unable to find affinity group by name " + groupName); + } else { + affinityGroupIds.add(groupId); + } + } + return affinityGroupIds; + } else { + return affinityGroupIdList; + } + } + + public String getKeyboard() { + // TODO Auto-generated method stub + return keyboard; + } + + public Map> getDhcpOptionsMap() { + Map> dhcpOptionsMap = new HashMap<>(); + if (dhcpOptionsNetworkList != null && !dhcpOptionsNetworkList.isEmpty()) { + + Collection> paramsCollection = this.dhcpOptionsNetworkList.values(); + for (Map dhcpNetworkOptions : paramsCollection) { + String networkId = dhcpNetworkOptions.get(ApiConstants.NETWORK_ID); + + if (networkId == null) { + throw new IllegalArgumentException("No networkid specified when providing extra dhcp options."); + } + + Map dhcpOptionsForNetwork = new HashMap<>(); + dhcpOptionsMap.put(networkId, dhcpOptionsForNetwork); + + for (String key : dhcpNetworkOptions.keySet()) { + if (key.startsWith(ApiConstants.DHCP_PREFIX)) { + int dhcpOptionValue = Integer.parseInt(key.replaceFirst(ApiConstants.DHCP_PREFIX, "")); + dhcpOptionsForNetwork.put(dhcpOptionValue, dhcpNetworkOptions.get(key)); + } else if (!key.equals(ApiConstants.NETWORK_ID)) { + Dhcp.DhcpOptionCode dhcpOptionEnum = Dhcp.DhcpOptionCode.valueOfString(key); + dhcpOptionsForNetwork.put(dhcpOptionEnum.getCode(), dhcpNetworkOptions.get(key)); + } + } + + } + } + + return dhcpOptionsMap; + } + + public Map getDataDiskTemplateToDiskOfferingMap() { + if (diskOfferingId != null && dataDiskTemplateToDiskOfferingList != null) { + throw new InvalidParameterValueException("diskofferingid parameter can't be specified along with datadisktemplatetodiskofferinglist parameter"); + } + if (MapUtils.isEmpty(dataDiskTemplateToDiskOfferingList)) { + return new HashMap(); + } + + HashMap dataDiskTemplateToDiskOfferingMap = new HashMap(); + for (Object objDataDiskTemplates : dataDiskTemplateToDiskOfferingList.values()) { + HashMap dataDiskTemplates = (HashMap) objDataDiskTemplates; + Long dataDiskTemplateId; + DiskOffering dataDiskOffering = null; + VirtualMachineTemplate dataDiskTemplate= _entityMgr.findByUuid(VirtualMachineTemplate.class, dataDiskTemplates.get("datadisktemplateid")); + if (dataDiskTemplate == null) { + dataDiskTemplate = _entityMgr.findById(VirtualMachineTemplate.class, dataDiskTemplates.get("datadisktemplateid")); + if (dataDiskTemplate == null) + throw new InvalidParameterValueException("Unable to translate and find entity with datadisktemplateid " + dataDiskTemplates.get("datadisktemplateid")); + } + dataDiskTemplateId = dataDiskTemplate.getId(); + dataDiskOffering = _entityMgr.findByUuid(DiskOffering.class, dataDiskTemplates.get("diskofferingid")); + if (dataDiskOffering == null) { + dataDiskOffering = _entityMgr.findById(DiskOffering.class, dataDiskTemplates.get("diskofferingid")); + if (dataDiskOffering == null) + throw new InvalidParameterValueException("Unable to translate and find entity with diskofferingId " + dataDiskTemplates.get("diskofferingid")); + } + dataDiskTemplateToDiskOfferingMap.put(dataDiskTemplateId, dataDiskOffering); + } + return dataDiskTemplateToDiskOfferingMap; + } + + public String getExtraConfig() { + return extraConfig; + } + + public boolean getCopyImageTags() { + return copyImageTags == null ? false : copyImageTags; + } + + public Boolean getBootIntoSetup() { + return bootIntoSetup; + } + + public boolean isDynamicScalingEnabled() { + return dynamicScalingEnabled == null ? true : dynamicScalingEnabled; + } + + public Long getOverrideDiskOfferingId() { + return overrideDiskOfferingId; + } + + public IoDriverPolicy getIoDriverPolicy() { + if (StringUtils.isNotBlank(ioDriverPolicy)) { + try { + String policyType = ioDriverPolicy.trim().toUpperCase(); + return IoDriverPolicy.valueOf(policyType); + } catch (IllegalArgumentException e) { + String errMesg = String.format("Invalid io policy %s specified for vm %s. Valid values are: %s", ioDriverPolicy, getName(), Arrays.toString(IoDriverPolicy.values())); + logger.warn(errMesg); + throw new InvalidParameterValueException(errMesg); + } + } + return null; + } + + public String getInstanceType() { + return null; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + + public static String getResultObjectName() { + return "virtualmachine"; + } + + @Override + public long getEntityOwnerId() { + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); + if (accountId == null) { + return CallContext.current().getCallingAccount().getId(); + } + + return accountId; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_VM_CREATE; + } + + @Override + public String getCreateEventType() { + return EventTypes.EVENT_VM_CREATE; + } + + @Override + public String getCreateEventDescription() { + return "Creating Instance"; + } + + @Override + public String getEventDescription() { + if(getStartVm()) { + return "Starting Instance with ID: " + getEntityUuid(); + } + return "Deploying Instance with ID: " + getEntityUuid(); + } + + @Override + public ApiCommandResourceType getApiResourceType() { + return ApiCommandResourceType.VirtualMachine; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java new file mode 100644 index 000000000000..7743c031006b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMFromBackupCmd.java @@ -0,0 +1,163 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.vm; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupResponse; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.TemplateResponse; +import org.apache.cloudstack.api.response.UserVmResponse; + +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InsufficientServerCapacityException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.uservm.UserVm; +import com.cloud.vm.VirtualMachine; +import org.apache.commons.lang3.ObjectUtils; + +@APICommand(name = "createVMFromBackup", + description = "Creates and automatically starts a VM from a backup.", + responseObject = UserVmResponse.class, + responseView = ResponseObject.ResponseView.Restricted, + entityType = {VirtualMachine.class}, + requestHasSensitiveInfo = false, responseHasSensitiveInfo = true, + since = "4.21.0", + authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) +public class CreateVMFromBackupCmd extends BaseDeployVMCmd { + + ///////////////////////////////////////////////////// + //////////////// API parameters ///////////////////// + ///////////////////////////////////////////////////// + + @ACL + @Parameter(name = ApiConstants.BACKUP_ID, + type = CommandType.UUID, + entityType = BackupResponse.class, + required = true, + description = "backup ID to create the VM from") + private Long backupId; + + @ACL + @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class, description = "the ID of the service offering for the virtual machine") + private Long serviceOfferingId; + + @ACL + @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "the ID of the template for the virtual machine") + private Long templateId; + + @Parameter(name = ApiConstants.PRESERVE_IP, type = CommandType.BOOLEAN, description = "Use the same IP/MAC addresses as stored in the backup metadata. Works only if the original Instance is deleted and the IP/MAC address is available.") + private Boolean preserveIp; + + @Parameter(name = ApiConstants.QUICK_RESTORE, type = CommandType.BOOLEAN, entityType = BackupResponse.class, description = "Whether to use the quick restore process or not. " + + "Currently this parameter is only supported by the KBOSS provider.", since = "4.23.0") + private Boolean quickRestore; + + ///////////////////////////////////////////////////// + /////////////////// Accessors /////////////////////// + ///////////////////////////////////////////////////// + + public Long getBackupId() { + return backupId; + } + + public Long getServiceOfferingId() { + return serviceOfferingId; + } + + public Long getTemplateId() { + return templateId; + } + + public boolean getPreserveIp() { + return (preserveIp != null) ? preserveIp : false; + } + + public Boolean getQuickRestore() { + return ObjectUtils.defaultIfNull(this.quickRestore, false); + } + + @Override + public void create() { + UserVm vm; + try { + vm = _userVmService.allocateVMFromBackup(this); + if (vm != null) { + setEntityId(vm.getId()); + setEntityUuid(vm.getUuid()); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to deploy vm"); + } + } catch (InsufficientCapacityException ex) { + logger.info(ex); + logger.trace(ex.getMessage(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, ex.getMessage()); + } catch (ResourceUnavailableException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (ResourceAllocationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + } + } + + @Override + public void execute () { + UserVm vm = null; + try { + vm = _userVmService.restoreVMFromBackup(this); + } catch (ResourceUnavailableException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_UNAVAILABLE_ERROR, ex.getMessage()); + } catch (ResourceAllocationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.RESOURCE_ALLOCATION_ERROR, ex.getMessage()); + } catch (ConcurrentOperationException ex) { + logger.warn("Exception: ", ex); + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); + } catch (InsufficientCapacityException ex) { + StringBuilder message = new StringBuilder(ex.getMessage()); + if (ex instanceof InsufficientServerCapacityException) { + if (((InsufficientServerCapacityException)ex).isAffinityApplied()) { + message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); + } + } + logger.info("{}: {}", message.toString(), ex.getLocalizedMessage()); + logger.debug(message.toString(), ex); + throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); + } + + if (vm != null) { + UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", vm).get(0); + response.setResponseName(getCommandName()); + setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to deploy vm uuid:"+getEntityUuid()); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java index 7e9bdd942ed7..1fce8caf8e1a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmd.java @@ -22,23 +22,26 @@ import com.cloud.vm.VirtualMachine; 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.BaseCmd; import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import javax.inject.Inject; import java.util.Date; +@Deprecated @APICommand(name = "createVMSchedule", description = "Create Instance Schedule", responseObject = VMScheduleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class CreateVMScheduleCmd extends BaseCmd { @Inject - VMScheduleManager vmScheduleManager; + ResourceScheduleManager resourceScheduleManager; @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, @@ -75,14 +78,14 @@ public class CreateVMScheduleCmd extends BaseCmd { type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active. Defaults to current date plus 1 minute." - + "Use format \"yyyy-MM-dd hh:mm:ss\")") + + "(Format \"yyyy-MM-dd hh:mm:ss\")") private Date startDate; @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive" - + "Use format \"yyyy-MM-dd hh:mm:ss\")") + + "(Format \"yyyy-MM-dd hh:mm:ss\")") private Date endDate; @Parameter(name = ApiConstants.ENABLED, @@ -91,9 +94,9 @@ public class CreateVMScheduleCmd extends BaseCmd { description = "Enable Instance schedule. Defaults to true") private Boolean enabled; - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// public Long getVmId() { return vmId; @@ -130,13 +133,19 @@ public Boolean getEnabled() { return enabled; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// @Override public void execute() { - VMScheduleResponse response = vmScheduleManager.createSchedule(this); + String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null; + + ResourceScheduleResponse scheduleResponse = resourceScheduleManager.createSchedule( + ApiCommandResourceType.VirtualMachine, + resourceIdStr, getDescription(), getSchedule(), getTimeZone(), getAction(), + getStartDate(), getEndDate(), getEnabled(), null); + VMScheduleResponse response = new VMScheduleResponse(scheduleResponse); response.setResponseName(getCommandName()); setResponseObject(response); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java index f34d07b045d9..f40db4ee0487 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmd.java @@ -22,6 +22,7 @@ import com.cloud.vm.VirtualMachine; 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; @@ -30,19 +31,19 @@ import org.apache.cloudstack.api.response.SuccessResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import javax.inject.Inject; import java.util.Collections; import java.util.List; +@Deprecated @APICommand(name = "deleteVMSchedule", description = "Delete Instance Schedule.", responseObject = SuccessResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class DeleteVMScheduleCmd extends BaseCmd { @Inject - VMScheduleManager vmScheduleManager; + ResourceScheduleManager resourceScheduleManager; @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, @@ -50,12 +51,14 @@ public class DeleteVMScheduleCmd extends BaseCmd { required = true, description = "ID of Instance") private Long vmId; + @Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VMScheduleResponse.class, required = false, description = "ID of Instance schedule") private Long id; + @Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, @@ -64,9 +67,9 @@ public class DeleteVMScheduleCmd extends BaseCmd { description = "IDs of Instance schedule") private List ids; - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// public Long getId() { return id; @@ -83,18 +86,21 @@ public Long getVmId() { return vmId; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// @Override public void execute() { - long rowsRemoved = vmScheduleManager.removeSchedule(this); + String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null; + long rowsRemoved = resourceScheduleManager.removeSchedule( + ApiCommandResourceType.VirtualMachine, + resourceIdStr, getId(), getIds()); if (rowsRemoved > 0) { final SuccessResponse response = new SuccessResponse(); response.setResponseName(getCommandName()); - response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase()); + response.setObjectName("vmschedule"); setResponseObject(response); } else { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to delete Instance Schedules"); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java index f08f906997cd..0a943fab118d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java @@ -16,769 +16,190 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; -import javax.annotation.Nonnull; - -import org.apache.cloudstack.acl.RoleType; -import org.apache.cloudstack.affinity.AffinityGroupResponse; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; -import org.apache.cloudstack.api.ApiArgValidator; -import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; import org.apache.cloudstack.api.ApiErrorCode; -import org.apache.cloudstack.api.BaseAsyncCreateCustomIdCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ResponseObject.ResponseView; import org.apache.cloudstack.api.ServerApiException; -import org.apache.cloudstack.api.command.user.UserCmd; -import org.apache.cloudstack.api.response.DiskOfferingResponse; -import org.apache.cloudstack.api.response.DomainResponse; -import org.apache.cloudstack.api.response.HostResponse; -import org.apache.cloudstack.api.response.NetworkResponse; -import org.apache.cloudstack.api.response.ProjectResponse; -import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.api.response.SnapshotResponse; import org.apache.cloudstack.api.response.TemplateResponse; -import org.apache.cloudstack.api.response.UserDataResponse; import org.apache.cloudstack.api.response.UserVmResponse; -import org.apache.cloudstack.api.response.ZoneResponse; +import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.context.CallContext; -import org.apache.commons.collections.CollectionUtils; -import org.apache.commons.collections.MapUtils; -import org.apache.commons.lang3.BooleanUtils; -import com.cloud.agent.api.LogLevel; -import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.InsufficientServerCapacityException; -import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.hypervisor.Hypervisor.HypervisorType; -import com.cloud.network.Network; -import com.cloud.network.Network.IpAddresses; -import com.cloud.offering.DiskOffering; -import com.cloud.template.VirtualMachineTemplate; import com.cloud.uservm.UserVm; -import com.cloud.utils.net.Dhcp; -import com.cloud.utils.net.NetUtils; -import com.cloud.utils.StringUtils; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VmDetailConstants; @APICommand(name = "deployVirtualMachine", description = "Creates and automatically starts an Instance based on a service offering, disk offering, and Template.", responseObject = UserVmResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class}, - requestHasSensitiveInfo = false, responseHasSensitiveInfo = true) -public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd implements SecurityGroupAction, UserCmd { - - private static final String s_name = "deployvirtualmachineresponse"; + requestHasSensitiveInfo = false) +public class DeployVMCmd extends BaseDeployVMCmd { ///////////////////////////////////////////////////// //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// - @Parameter(name = ApiConstants.ZONE_ID, type = CommandType.UUID, entityType = ZoneResponse.class, required = true, description = "Availability zone for the Instance") - private Long zoneId; - @ACL @Parameter(name = ApiConstants.SERVICE_OFFERING_ID, type = CommandType.UUID, entityType = ServiceOfferingResponse.class, required = true, description = "The ID of the Service offering for the Instance") private Long serviceOfferingId; @ACL - @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, required = true, description = "The ID of the Template for the Instance") + @Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID, entityType = TemplateResponse.class, description = "The ID of the Template for the Instance") private Long templateId; - @Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "Host name for the Instance", validations = {ApiArgValidator.RFCComplianceDomainName}) - private String name; - - @Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING, description = "An optional User generated name for the Instance") - private String displayName; - - @Parameter(name=ApiConstants.PASSWORD, type=CommandType.STRING, description="The password of the virtual machine. If null, a random password will be generated for the VM.", - since="4.19.0.0") - protected String password; - - //Owner information - @Parameter(name = ApiConstants.ACCOUNT, type = CommandType.STRING, description = "An optional Account for the Instance. Must be used with domainId.") - private String accountName; - - @Parameter(name = ApiConstants.DOMAIN_ID, type = CommandType.UUID, entityType = DomainResponse.class, description = "An optional domainId for the Instance. If the Account parameter is used, domainId must also be used. If Account is NOT provided then Instance will be assigned to the caller Account and domain.") - private Long domainId; - - //Network information - //@ACL(accessType = AccessType.UseEntry) - @Parameter(name = ApiConstants.NETWORK_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = NetworkResponse.class, description = "List of Network IDs used by Instance. Can't be specified with ipToNetworkList parameter") - private List networkIds; - - @Parameter(name = ApiConstants.BOOT_TYPE, type = CommandType.STRING, required = false, description = "Guest Instance Boot option either custom[UEFI] or default boot [BIOS]. Not applicable with VMware if the Template is marked as deploy-as-is, as we honour what is defined in the Template.", since = "4.14.0.0") - private String bootType; - - @Parameter(name = ApiConstants.BOOT_MODE, type = CommandType.STRING, required = false, description = "Boot Mode [Legacy] or [Secure] Applicable when Boot Type Selected is UEFI, otherwise Legacy only for BIOS. Not applicable with VMWare if the Template is marked as deploy-as-is, as we honour what is defined in the Template.", since = "4.14.0.0") - private String bootMode; - - @Parameter(name = ApiConstants.BOOT_INTO_SETUP, type = CommandType.BOOLEAN, required = false, description = "Boot into hardware setup or not (ignored if startVm = false, only valid for VMWare)", since = "4.15.0.0") - private Boolean bootIntoSetup; - - //DataDisk information - @ACL - @Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "The ID of the disk offering for the Instance. If the Template is of ISO format," - + " the diskOfferingId is for the root disk volume. Otherwise this parameter is used to indicate the " - + "offering for the data disk volume. If the templateId parameter passed is from a Template object," - + " the diskOfferingId refers to a DATA Disk Volume created. If the templateId parameter passed is " - + "from an ISO object, the diskOfferingId refers to a ROOT Disk Volume created.") - private Long diskOfferingId; - - @Parameter(name = ApiConstants.SIZE, type = CommandType.LONG, description = "The arbitrary size for the DATADISK volume. Mutually exclusive with diskOfferingId") - private Long size; - - @Parameter(name = ApiConstants.ROOT_DISK_SIZE, - type = CommandType.LONG, - description = "Optional field to resize root disk on deploy. Value is in GB. Only applies to template-based deployments. Analogous to details[0].rootdisksize, which takes precedence over this parameter if both are provided", - since = "4.4") - private Long rootdisksize; - - @Parameter(name = ApiConstants.GROUP, type = CommandType.STRING, description = "An optional group for the Instance") - private String group; - - @Parameter(name = ApiConstants.HYPERVISOR, type = CommandType.STRING, description = "The hypervisor on which to deploy the Instance. " - + "The parameter is required and respected only when hypervisor info is not set on the ISO/Template passed to the call") - private String hypervisor; - - @Parameter(name = ApiConstants.USER_DATA, type = CommandType.STRING, - description = "An optional binary data that can be sent to the Instance upon a successful deployment. " + - "This binary data must be base64 encoded before adding it to the request. " + - "Using HTTP GET (via querystring), you can send up to 4KB of data after base64 encoding. " + - "Using HTTP POST (via POST body), you can send up to 1MB of data after base64 encoding. " + - "You also need to change vm.userdata.max.length value", - length = 1048576) - private String userData; - - @Parameter(name = ApiConstants.USER_DATA_ID, type = CommandType.UUID, entityType = UserDataResponse.class, description = "The ID of the Userdata", since = "4.18") - private Long userdataId; - - @Parameter(name = ApiConstants.USER_DATA_DETAILS, type = CommandType.MAP, description = "Used to specify the parameters values for the variables in userdata.", since = "4.18") - private Map userdataDetails; - - @Deprecated - @Parameter(name = ApiConstants.SSH_KEYPAIR, type = CommandType.STRING, description = "Name of the SSH key pair used to login to the Instance") - private String sshKeyPairName; - - @Parameter(name = ApiConstants.SSH_KEYPAIRS, type = CommandType.LIST, collectionType = CommandType.STRING, since="4.17", description = "Names of the SSH key pairs used to login to the Instance") - private List sshKeyPairNames; - - @Parameter(name = ApiConstants.HOST_ID, type = CommandType.UUID, entityType = HostResponse.class, description = "Destination Host ID to deploy the Instance to - parameter available for root admin only") - private Long hostId; - - @ACL - @Parameter(name = ApiConstants.SECURITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = SecurityGroupResponse.class, description = "Comma separated list of security groups id that going to be applied to the Instance. " - + "Should be passed only when Instance is created from a zone with Basic Network support." + " Mutually exclusive with securitygroupnames parameter") - private List securityGroupIdList; - - @ACL - @Parameter(name = ApiConstants.SECURITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = SecurityGroupResponse.class, description = "Comma separated list of security groups names that going to be applied to the Instance." - + " Should be passed only when Instance is created from a zone with Basic Network support. " + "Mutually exclusive with securitygroupids parameter") - private List securityGroupNameList; - - @Parameter(name = ApiConstants.IP_NETWORK_LIST, type = CommandType.MAP, description = "IP to network mapping. Can't be specified with networkIds parameter." - + " Example: iptonetworklist[0].ip=10.10.10.11&iptonetworklist[0].ipv6=fc00:1234:5678::abcd&iptonetworklist[0].networkid=uuid&iptonetworklist[0].mac=aa:bb:cc:dd:ee::ff - requests to use IP 10.10.10.11 in network id=uuid") - private Map ipToNetworkList; - - @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, description = "The IP address for default Instance's Network") - private String ipAddress; - - @Parameter(name = ApiConstants.IP6_ADDRESS, type = CommandType.STRING, description = "The IPv6 address for default Instance's Network") - private String ip6Address; - - @Parameter(name = ApiConstants.MAC_ADDRESS, type = CommandType.STRING, description = "The MAC address for default Instance's Network") - private String macAddress; - - @Parameter(name = ApiConstants.KEYBOARD, type = CommandType.STRING, description = "An optional keyboard device type for the Instance. valid value can be one of de,de-ch,es,fi,fr,fr-be,fr-ch,is,it,jp,nl-be,no,pt,uk,us") - private String keyboard; + @Parameter(name = ApiConstants.VOLUME_ID, type = CommandType.UUID, entityType = VolumeResponse.class, since = "4.21") + private Long volumeId; - @Parameter(name = ApiConstants.PROJECT_ID, type = CommandType.UUID, entityType = ProjectResponse.class, description = "Deploy Instance for the project") - private Long projectId; - - @Parameter(name = ApiConstants.START_VM, type = CommandType.BOOLEAN, description = "True if start Instance after creating; defaulted to true if not specified") - private Boolean startVm; - - @ACL - @Parameter(name = ApiConstants.AFFINITY_GROUP_IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = AffinityGroupResponse.class, description = "Comma separated list of affinity groups id that are going to be applied to the Instance." - + " Mutually exclusive with affinitygroupnames parameter") - private List affinityGroupIdList; - - @ACL - @Parameter(name = ApiConstants.AFFINITY_GROUP_NAMES, type = CommandType.LIST, collectionType = CommandType.STRING, entityType = AffinityGroupResponse.class, description = "Comma separated list of affinity groups names that are going to be applied to the Instance." - + "Mutually exclusive with affinitygroupids parameter") - private List affinityGroupNameList; - - @Parameter(name = ApiConstants.DISPLAY_VM, type = CommandType.BOOLEAN, since = "4.2", description = "An optional field, whether to the display the Instance to the end user or not.", authorized = {RoleType.Admin}) - private Boolean displayVm; - - @Parameter(name = ApiConstants.DETAILS, type = CommandType.MAP, since = "4.3", description = "Used to specify the custom parameters. 'extraconfig' is not allowed to be passed in details") - private Map details; - - @Parameter(name = ApiConstants.DEPLOYMENT_PLANNER, type = CommandType.STRING, description = "Deployment planner to use for Instance allocation. Available to ROOT admin only", since = "4.4", authorized = { RoleType.Admin }) - private String deploymentPlanner; - - @Parameter(name = ApiConstants.DHCP_OPTIONS_NETWORK_LIST, type = CommandType.MAP, description = "DHCP options which are passed to the Instance on start up" - + " Example: dhcpoptionsnetworklist[0].dhcp:114=url&dhcpoptionsetworklist[0].networkid=networkid&dhcpoptionsetworklist[0].dhcp:66=www.test.com") - private Map dhcpOptionsNetworkList; - - @Parameter(name = ApiConstants.DATADISK_OFFERING_LIST, type = CommandType.MAP, since = "4.11", description = "Datadisk Template to disk-offering mapping;" + - " an optional parameter used to create additional data disks from datadisk Templates; can't be specified with diskOfferingId parameter") - private Map dataDiskTemplateToDiskOfferingList; - - @Parameter(name = ApiConstants.EXTRA_CONFIG, type = CommandType.STRING, since = "4.12", description = "An optional URL encoded string that can be passed to the Instance upon successful deployment", length = 5120) - private String extraConfig; - - @Parameter(name = ApiConstants.COPY_IMAGE_TAGS, type = CommandType.BOOLEAN, since = "4.13", description = "If true the image tags (if any) will be copied to the Instance, default value is false") - private Boolean copyImageTags; - - @Parameter(name = ApiConstants.PROPERTIES, type = CommandType.MAP, since = "4.15", - description = "Used to specify the vApp properties.") - @LogLevel(LogLevel.Log4jLevel.Off) - private Map vAppProperties; - - @Parameter(name = ApiConstants.NIC_NETWORK_LIST, type = CommandType.MAP, since = "4.15", - description = "VMware only: used to specify network mapping of a vApp VMware Template registered \"as-is\"." + - " Example nicnetworklist[0].ip=Nic-101&nicnetworklist[0].network=uuid") - @LogLevel(LogLevel.Log4jLevel.Off) - private Map vAppNetworks; - - @Parameter(name = ApiConstants.DYNAMIC_SCALING_ENABLED, type = CommandType.BOOLEAN, since = "4.16", - description = "True if Instance needs to be dynamically scalable") - protected Boolean dynamicScalingEnabled; - - @Parameter(name = ApiConstants.OVERRIDE_DISK_OFFERING_ID, type = CommandType.UUID, since = "4.17", entityType = DiskOfferingResponse.class, description = "The ID of the disk offering for the Instance to be used for root volume instead of the disk offering mapped in service offering." + - "In case of Instance deploying from ISO, then the diskofferingid specified for root volume is ignored and uses this override disk offering id") - private Long overrideDiskOfferingId; - - @Parameter(name = ApiConstants.IOTHREADS_ENABLED, type = CommandType.BOOLEAN, required = false, - description = "IOThreads are dedicated event loop threads for supported disk devices to perform block I/O requests in order to improve scalability especially on an SMP host/guest with many LUNs.") - private Boolean iothreadsEnabled; - - @Parameter(name = ApiConstants.IO_DRIVER_POLICY, type = CommandType.STRING, description = "Controls specific policies on IO") - private String ioDriverPolicy; - - @Parameter(name = ApiConstants.NIC_MULTIQUEUE_NUMBER, type = CommandType.INTEGER, since = "4.18", - description = "The number of queues for multiqueue NICs.") - private Integer nicMultiqueueNumber; - - @Parameter(name = ApiConstants.NIC_PACKED_VIRTQUEUES_ENABLED, type = CommandType.BOOLEAN, since = "4.18", - description = "Enable packed virtqueues or not.") - private Boolean nicPackedVirtQueues; + @Parameter(name = ApiConstants.SNAPSHOT_ID, type = CommandType.UUID, entityType = SnapshotResponse.class, since = "4.21") + private Long snapshotId; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public String getAccountName() { - if (accountName == null) { - return CallContext.current().getCallingAccount().getAccountName(); - } - return accountName; - } - - public Long getDiskOfferingId() { - return diskOfferingId; - } - - public String getDeploymentPlanner() { - return deploymentPlanner; - } - - public String getDisplayName() { - if (StringUtils.isEmpty(displayName)) { - displayName = name; - } - return displayName; - } - - public Long getDomainId() { - if (domainId == null) { - return CallContext.current().getCallingAccount().getDomainId(); - } - return domainId; - } - - public ApiConstants.BootType getBootType() { - if (StringUtils.isNotBlank(bootType)) { - try { - String type = bootType.trim().toUpperCase(); - return ApiConstants.BootType.valueOf(type); - } catch (IllegalArgumentException e) { - String errMesg = "Invalid bootType " + bootType + "Specified for Instance " + getName() - + " Valid values are: " + Arrays.toString(ApiConstants.BootType.values()); - logger.warn(errMesg); - throw new InvalidParameterValueException(errMesg); - } - } - return null; - } - - public Map getDetails() { - Map customparameterMap = convertDetailsToMap(details); - - if (getBootType() != null) { - customparameterMap.put(getBootType().toString(), getBootMode().toString()); - } - - if (rootdisksize != null && !customparameterMap.containsKey(VmDetailConstants.ROOT_DISK_SIZE)) { - customparameterMap.put(VmDetailConstants.ROOT_DISK_SIZE, rootdisksize.toString()); - } - - IoDriverPolicy ioPolicy = getIoDriverPolicy(); - if (ioPolicy != null) { - customparameterMap.put(VmDetailConstants.IO_POLICY, ioPolicy.toString()); - } - - if (BooleanUtils.toBoolean(iothreadsEnabled)) { - customparameterMap.put(VmDetailConstants.IOTHREADS, BooleanUtils.toStringTrueFalse(iothreadsEnabled)); - } - - if (nicMultiqueueNumber != null) { - customparameterMap.put(VmDetailConstants.NIC_MULTIQUEUE_NUMBER, nicMultiqueueNumber.toString()); - } - - if (BooleanUtils.toBoolean(nicPackedVirtQueues)) { - customparameterMap.put(VmDetailConstants.NIC_PACKED_VIRTQUEUES_ENABLED, BooleanUtils.toStringTrueFalse(nicPackedVirtQueues)); - } - - return customparameterMap; - } - - - public ApiConstants.BootMode getBootMode() { - if (StringUtils.isNotBlank(bootMode)) { - try { - String mode = bootMode.trim().toUpperCase(); - return ApiConstants.BootMode.valueOf(mode); - } catch (IllegalArgumentException e) { - String msg = String.format("Invalid %s: %s specified for Instance: %s. Valid values are: %s", - ApiConstants.BOOT_MODE, bootMode, getName(), Arrays.toString(ApiConstants.BootMode.values())); - logger.error(msg); - throw new InvalidParameterValueException(msg); - } - } - if (ApiConstants.BootType.UEFI.equals(getBootType())) { - String msg = String.format("%s must be specified for the Instance with boot type: %s. Valid values are: %s", - ApiConstants.BOOT_MODE, getBootType(), Arrays.toString(ApiConstants.BootMode.values())); - logger.error(msg); - throw new InvalidParameterValueException(msg); - } - return null; - } - - public Map getVmProperties() { - Map map = new HashMap<>(); - if (MapUtils.isNotEmpty(vAppProperties)) { - Collection parameterCollection = vAppProperties.values(); - Iterator iterator = parameterCollection.iterator(); - while (iterator.hasNext()) { - HashMap entry = (HashMap)iterator.next(); - map.put(entry.get("key"), entry.get("value")); - } - } - return map; - } - - public Map getVmNetworkMap() { - Map map = new HashMap<>(); - if (MapUtils.isNotEmpty(vAppNetworks)) { - Collection parameterCollection = vAppNetworks.values(); - Iterator iterator = parameterCollection.iterator(); - while (iterator.hasNext()) { - HashMap entry = (HashMap) iterator.next(); - Integer nic; - try { - nic = Integer.valueOf(entry.get(VmDetailConstants.NIC)); - } catch (NumberFormatException nfe) { - nic = null; - } - String networkUuid = entry.get(VmDetailConstants.NETWORK); - if (logger.isTraceEnabled()) { - logger.trace(String.format("nic, '%s', goes on net, '%s'", nic, networkUuid)); - } - if (nic == null || StringUtils.isEmpty(networkUuid) || _entityMgr.findByUuid(Network.class, networkUuid) == null) { - throw new InvalidParameterValueException(String.format("Network ID: %s for NIC ID: %s is invalid", networkUuid, nic)); - } - map.put(nic, _entityMgr.findByUuid(Network.class, networkUuid).getId()); - } - } - return map; - } - - public String getGroup() { - return group; - } - - public HypervisorType getHypervisor() { - return HypervisorType.getType(hypervisor); - } - - public Boolean isDisplayVm() { - return displayVm; - } - - @Override - public boolean isDisplay() { - if(displayVm == null) - return true; - else - return displayVm; - } - - public List getSecurityGroupNameList() { - return securityGroupNameList; - } - - public List getSecurityGroupIdList() { - return securityGroupIdList; - } - public Long getServiceOfferingId() { return serviceOfferingId; } - public Long getSize() { - return size; - } - public Long getTemplateId() { return templateId; } - public String getUserData() { - return userData; + public Long getVolumeId() { + return volumeId; } - public Long getUserdataId() { - return userdataId; + public Long getSnapshotId() { + return snapshotId; } - public Map getUserdataDetails() { - return convertDetailsToMap(userdataDetails); + public boolean isVolumeOrSnapshotProvided() { + return volumeId != null || snapshotId != null; } - public Long getZoneId() { - return zoneId; + public boolean isBlankInstance() { + return false; } - public String getPassword() { - return password; - } - public List getNetworkIds() { - if (MapUtils.isNotEmpty(vAppNetworks)) { - if (CollectionUtils.isNotEmpty(networkIds) || ipAddress != null || getIp6Address() != null || MapUtils.isNotEmpty(ipToNetworkList)) { - throw new InvalidParameterValueException(String.format("%s can't be specified along with %s, %s, %s", ApiConstants.NIC_NETWORK_LIST, ApiConstants.NETWORK_IDS, ApiConstants.IP_ADDRESS, ApiConstants.IP_NETWORK_LIST)); - } else { - return new ArrayList<>(); - } - } - if (ipToNetworkList != null && !ipToNetworkList.isEmpty()) { - if ((networkIds != null && !networkIds.isEmpty()) || ipAddress != null || getIp6Address() != null) { - throw new InvalidParameterValueException("ipToNetworkMap can't be specified along with networkIds or ipAddress"); - } else { - List networks = new ArrayList(); - networks.addAll(getIpToNetworkMap().keySet()); - return networks; - } - } - return networkIds; - } - - public String getName() { - return name; - } - - public List getSSHKeyPairNames() { - List sshKeyPairs = new ArrayList(); - if(sshKeyPairNames != null) { - sshKeyPairs = sshKeyPairNames; - } - if(sshKeyPairName != null && !sshKeyPairName.isEmpty()) { - sshKeyPairs.add(sshKeyPairName); - } - return sshKeyPairs; - } - public Long getHostId() { - return hostId; + ///////////////////////////////////////////////////// + ////////////////// Setters ////////////////////////// + ///////////////////////////////////////////////////// + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; } - public boolean getStartVm() { - return startVm == null ? true : startVm; + public void setName(String name) { + this.name = name; } - public Map getIpToNetworkMap() { - if ((networkIds != null || ipAddress != null || getIp6Address() != null) && ipToNetworkList != null) { - throw new InvalidParameterValueException("NetworkIds and ipAddress can't be specified along with ipToNetworkMap parameter"); - } - LinkedHashMap ipToNetworkMap = null; - if (ipToNetworkList != null && !ipToNetworkList.isEmpty()) { - ipToNetworkMap = new LinkedHashMap(); - Collection ipsCollection = ipToNetworkList.values(); - Iterator iter = ipsCollection.iterator(); - while (iter.hasNext()) { - HashMap ips = (HashMap)iter.next(); - Long networkId = getNetworkIdFomIpMap(ips); - IpAddresses addrs = getIpAddressesFromIpMap(ips); - ipToNetworkMap.put(networkId, addrs); - } - } - - return ipToNetworkMap; + public void setDisplayName(String displayName) { + this.displayName = displayName; } - @Nonnull - private IpAddresses getIpAddressesFromIpMap(HashMap ips) { - String requestedIp = ips.get("ip"); - String requestedIpv6 = ips.get("ipv6"); - String requestedMac = ips.get("mac"); - if (requestedIpv6 != null) { - requestedIpv6 = NetUtils.standardizeIp6Address(requestedIpv6); - } - if (requestedMac != null) { - if(!NetUtils.isValidMac(requestedMac)) { - throw new InvalidParameterValueException("Mac address is not valid: " + requestedMac); - } else if(!NetUtils.isUnicastMac(requestedMac)) { - throw new InvalidParameterValueException("Mac address is not unicast: " + requestedMac); - } - requestedMac = NetUtils.standardizeMacAddress(requestedMac); - } - return new IpAddresses(requestedIp, requestedIpv6, requestedMac); + public void setAccountName(String accountName) { + this.accountName = accountName; } - @Nonnull - private Long getNetworkIdFomIpMap(HashMap ips) { - Long networkId; - final String networkid = ips.get("networkid"); - Network network = _networkService.getNetwork(networkid); - if (network != null) { - networkId = network.getId(); - } else { - try { - networkId = Long.parseLong(networkid); - } catch (NumberFormatException e) { - throw new InvalidParameterValueException("Unable to translate and find entity with networkId: " + networkid); - } - } - return networkId; + public void setDomainId(Long domainId) { + this.domainId = domainId; } - public String getIpAddress() { - return ipAddress; + public void setNetworkIds(List networkIds) { + this.networkIds = networkIds; } - public String getIp6Address() { - if (ip6Address == null) { - return null; - } - return NetUtils.standardizeIp6Address(ip6Address); + public void setBootType(String bootType) { + this.bootType = bootType; } - - public String getMacAddress() { - if (macAddress == null) { - return null; - } - if(!NetUtils.isValidMac(macAddress)) { - throw new InvalidParameterValueException("Mac address is not valid: " + macAddress); - } else if(!NetUtils.isUnicastMac(macAddress)) { - throw new InvalidParameterValueException("Mac address is not unicast: " + macAddress); - } - return NetUtils.standardizeMacAddress(macAddress); + public void setBootMode(String bootMode) { + this.bootMode = bootMode; } - public List getAffinityGroupIdList() { - if (affinityGroupNameList != null && affinityGroupIdList != null) { - throw new InvalidParameterValueException("affinitygroupids parameter is mutually exclusive with affinitygroupnames parameter"); - } - - // transform group names to ids here - if (affinityGroupNameList != null) { - List affinityGroupIds = new ArrayList(); - for (String groupName : affinityGroupNameList) { - Long groupId = _responseGenerator.getAffinityGroupId(groupName, getEntityOwnerId()); - if (groupId == null) { - throw new InvalidParameterValueException("Unable to find affinity group by name " + groupName); - } else { - affinityGroupIds.add(groupId); - } - } - return affinityGroupIds; - } else { - return affinityGroupIdList; - } + public void setHypervisor(String hypervisor) { + this.hypervisor = hypervisor; } - public String getKeyboard() { - // TODO Auto-generated method stub - return keyboard; + public void setUserData(String userData) { + this.userData = userData; } - public Map> getDhcpOptionsMap() { - Map> dhcpOptionsMap = new HashMap<>(); - if (dhcpOptionsNetworkList != null && !dhcpOptionsNetworkList.isEmpty()) { - - Collection> paramsCollection = this.dhcpOptionsNetworkList.values(); - for (Map dhcpNetworkOptions : paramsCollection) { - String networkId = dhcpNetworkOptions.get(ApiConstants.NETWORK_ID); - - if (networkId == null) { - throw new IllegalArgumentException("No networkid specified when providing extra dhcp options."); - } - - Map dhcpOptionsForNetwork = new HashMap<>(); - dhcpOptionsMap.put(networkId, dhcpOptionsForNetwork); - - for (String key : dhcpNetworkOptions.keySet()) { - if (key.startsWith(ApiConstants.DHCP_PREFIX)) { - int dhcpOptionValue = Integer.parseInt(key.replaceFirst(ApiConstants.DHCP_PREFIX, "")); - dhcpOptionsForNetwork.put(dhcpOptionValue, dhcpNetworkOptions.get(key)); - } else if (!key.equals(ApiConstants.NETWORK_ID)) { - Dhcp.DhcpOptionCode dhcpOptionEnum = Dhcp.DhcpOptionCode.valueOfString(key); - dhcpOptionsForNetwork.put(dhcpOptionEnum.getCode(), dhcpNetworkOptions.get(key)); - } - } - - } - } - - return dhcpOptionsMap; + public void setKeyboard(String keyboard) { + this.keyboard = keyboard; } - public Map getDataDiskTemplateToDiskOfferingMap() { - if (diskOfferingId != null && dataDiskTemplateToDiskOfferingList != null) { - throw new InvalidParameterValueException("diskofferingid parameter can't be specified along with datadisktemplatetodiskofferinglist parameter"); - } - if (MapUtils.isEmpty(dataDiskTemplateToDiskOfferingList)) { - return new HashMap(); - } - - HashMap dataDiskTemplateToDiskOfferingMap = new HashMap(); - for (Object objDataDiskTemplates : dataDiskTemplateToDiskOfferingList.values()) { - HashMap dataDiskTemplates = (HashMap) objDataDiskTemplates; - Long dataDiskTemplateId; - DiskOffering dataDiskOffering = null; - VirtualMachineTemplate dataDiskTemplate= _entityMgr.findByUuid(VirtualMachineTemplate.class, dataDiskTemplates.get("datadisktemplateid")); - if (dataDiskTemplate == null) { - dataDiskTemplate = _entityMgr.findById(VirtualMachineTemplate.class, dataDiskTemplates.get("datadisktemplateid")); - if (dataDiskTemplate == null) - throw new InvalidParameterValueException("Unable to translate and find entity with datadisktemplateid " + dataDiskTemplates.get("datadisktemplateid")); - } - dataDiskTemplateId = dataDiskTemplate.getId(); - dataDiskOffering = _entityMgr.findByUuid(DiskOffering.class, dataDiskTemplates.get("diskofferingid")); - if (dataDiskOffering == null) { - dataDiskOffering = _entityMgr.findById(DiskOffering.class, dataDiskTemplates.get("diskofferingid")); - if (dataDiskOffering == null) - throw new InvalidParameterValueException("Unable to translate and find entity with diskofferingId " + dataDiskTemplates.get("diskofferingid")); - } - dataDiskTemplateToDiskOfferingMap.put(dataDiskTemplateId, dataDiskOffering); - } - return dataDiskTemplateToDiskOfferingMap; + public void setProjectId(Long projectId) { + this.projectId = projectId; } - public String getExtraConfig() { - return extraConfig; + public void setDisplayVm(Boolean displayVm) { + this.displayVm = displayVm; } - public boolean getCopyImageTags() { - return copyImageTags == null ? false : copyImageTags; + public void setUserDataId(Long userDataId) { + this.userdataId = userDataId; } - public Boolean getBootIntoSetup() { - return bootIntoSetup; + public void setAffinityGroupIds(List ids) { + this.affinityGroupIdList = ids; } - public boolean isDynamicScalingEnabled() { - return dynamicScalingEnabled == null ? true : dynamicScalingEnabled; + public void setDetails(Map details) { + this.details = details; } - public Long getOverrideDiskOfferingId() { - return overrideDiskOfferingId; + public void setExtraConfig(String extraConfig) { + this.extraConfig = extraConfig; } - public ApiConstants.IoDriverPolicy getIoDriverPolicy() { - if (StringUtils.isNotBlank(ioDriverPolicy)) { - try { - String policyType = ioDriverPolicy.trim().toUpperCase(); - return ApiConstants.IoDriverPolicy.valueOf(policyType); - } catch (IllegalArgumentException e) { - String errMesg = String.format("Invalid I/O policy %s specified for Instance %s. Valid values are: %s", ioDriverPolicy, getName(), Arrays.toString(ApiConstants.IoDriverPolicy.values())); - logger.warn(errMesg); - throw new InvalidParameterValueException(errMesg); - } - } - return null; + public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { + this.dynamicScalingEnabled = dynamicScalingEnabled; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - @Override - public String getCommandName() { - return s_name; + public void setServiceOfferingId(Long serviceOfferingId) { + this.serviceOfferingId = serviceOfferingId; } - public static String getResultObjectName() { - return "virtualmachine"; + public void setTemplateId(Long templateId) { + this.templateId = templateId; } - @Override - public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); - if (accountId == null) { - return CallContext.current().getCallingAccount().getId(); - } - - return accountId; + public void setVolumeId(Long volumeId) { + this.volumeId = volumeId; } - @Override - public String getEventType() { - return EventTypes.EVENT_VM_CREATE; + public void setSnapshotId(Long snapshotId) { + this.snapshotId = snapshotId; } - @Override - public String getCreateEventType() { - return EventTypes.EVENT_VM_CREATE; + public void setSshKeyPairNames(List sshKeyPairNames) { + this.sshKeyPairNames = sshKeyPairNames; } - @Override - public String getCreateEventDescription() { - return "creating Instance"; - } - - @Override - public String getEventDescription() { - if(getStartVm()) { - return "starting Instance. Instance Id: " + getEntityUuid(); - } - return "deploying Instance. Instance Id: " + getEntityUuid(); - } - - @Override - public ApiCommandResourceType getApiResourceType() { - return ApiCommandResourceType.VirtualMachine; + public void setSecurityGroupList(List securityGroupIdList) { + this.securityGroupIdList = securityGroupIdList; } @Override public void execute() { UserVm result; - CallContext.current().setEventDetails("Instance Id: " + getEntityUuid()); + CallContext.current().setEventDetails("Instance ID: " + getEntityUuid()); if (getStartVm()) { try { result = _userVmService.startVirtualMachine(this); @@ -798,12 +219,12 @@ public void execute() { message.append(", Please check the affinity groups provided, there may not be sufficient capacity to follow them"); } } - logger.info(String.format("%s: %s", message.toString(), ex.getLocalizedMessage())); + logger.info("{}: {}", message.toString(), ex.getLocalizedMessage()); logger.debug(message.toString(), ex); throw new ServerApiException(ApiErrorCode.INSUFFICIENT_CAPACITY_ERROR, message.toString()); } } else { - logger.info("Instance " + getEntityUuid() + " already created, load UserVm from DB"); + logger.info("Instance {} already created, load UserVm from DB", getEntityUuid()); result = _userVmService.finalizeCreateVirtualMachine(getEntityId()); } @@ -816,9 +237,12 @@ public void execute() { } } - @Override public void create() throws ResourceAllocationException { + if (!isBlankInstance() && Stream.of(templateId, snapshotId, volumeId).filter(Objects::nonNull).count() != 1) { + throw new CloudRuntimeException("Please provide only one of the following parameters - template ID, volume ID or snapshot ID"); + } + try { UserVm vm = _userVmService.createVirtualMachine(this); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java index 1ca73c0cb3cd..0a3e510d3d53 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DestroyVMCmd.java @@ -90,10 +90,22 @@ public List getVolumeIds() { return volumeIds; } + public boolean isForced() { + return false; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// + public DestroyVMCmd() { + } + + public DestroyVMCmd(Long id, Boolean expunge) { + this.id = id; + this.expunge = expunge; + } + @Override public String getCommandName() { return s_name; @@ -116,7 +128,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "destroying Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Destroying Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -131,8 +143,8 @@ public Long getApiResourceId() { @Override public void execute() throws ResourceUnavailableException, ConcurrentOperationException { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); - UserVm result = _userVmService.destroyVm(this); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); + UserVm result = _userVmService.destroyVm(this, true); UserVmResponse response = new UserVmResponse(); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java index be94315abe76..c7a9a75f2eac 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmd.java @@ -20,23 +20,27 @@ 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.BaseListCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; +@Deprecated @APICommand(name = "listVMSchedule", description = "List Instance Schedules.", responseObject = VMScheduleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class ListVMScheduleCmd extends BaseListCmd { @Inject - VMScheduleManager vmScheduleManager; + ResourceScheduleManager resourceScheduleManager; @Parameter(name = ApiConstants.VIRTUAL_MACHINE_ID, type = CommandType.UUID, @@ -61,12 +65,12 @@ public class ListVMScheduleCmd extends BaseListCmd { @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, required = false, - description = "ID of Instance schedule") + description = "Filter by enabled status") private Boolean enabled; - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// public Long getVmId() { return vmId; @@ -84,14 +88,26 @@ public Boolean getEnabled() { return enabled; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// @Override public void execute() { - ListResponse response = vmScheduleManager.listSchedule(this); + String resourceIdStr = getVmId() != null ? String.valueOf(getVmId()) : null; + + ListResponse scheduleResponse = resourceScheduleManager.listSchedule( + getId(), null, ApiCommandResourceType.VirtualMachine, resourceIdStr, getAction(), getEnabled(), + getStartIndex(), getPageSizeVal() + ); + + List vmScheduleResponses = new ArrayList<>(); + for (ResourceScheduleResponse resourceScheduleResponse : scheduleResponse.getResponses()) { + vmScheduleResponses.add(new VMScheduleResponse(resourceScheduleResponse)); + } + ListResponse response = new ListResponse<>(); + response.setResponses(vmScheduleResponses, scheduleResponse.getCount()); response.setResponseName(getCommandName()); - response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase()); + response.setObjectName("vmschedule"); setResponseObject(response); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java index 397669995ce2..46e3c7926f4e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmd.java @@ -16,10 +16,15 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; +import java.util.ArrayList; import java.util.EnumSet; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.affinity.AffinityGroupResponse; @@ -33,6 +38,7 @@ import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.AutoScaleVmGroupResponse; import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.api.response.ExtensionResponse; import org.apache.cloudstack.api.response.InstanceGroupResponse; import org.apache.cloudstack.api.response.IsoVmResponse; import org.apache.cloudstack.api.response.ListResponse; @@ -54,6 +60,7 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.server.ResourceIcon; import com.cloud.server.ResourceTag; +import com.cloud.storage.GuestOS; import com.cloud.vm.VirtualMachine; @@ -160,6 +167,22 @@ public class ListVMsCmd extends BaseListRetrieveOnlyResourceCountCmd implements since = "4.20.1") private String arch; + @Parameter(name = ApiConstants.LEASED, type = CommandType.BOOLEAN, + description = "Whether to return only leased instances", + since = "4.21.0") + private Boolean onlyLeasedInstances = false; + + @Parameter(name = ApiConstants.GPU_ENABLED, + type = CommandType.BOOLEAN, + description = "Flag to indicate if the VMs should be filtered by GPU support. If set to true, only VMs that support GPU will be returned.", + since = "4.21.0") + private Boolean gpuEnabled; + + @Parameter(name = ApiConstants.EXTENSION_ID, type = CommandType.UUID, + entityType = ExtensionResponse.class, description = "The ID of the Orchestrator extension for the VM", + since = "4.21.0") + private Long extensionId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -303,6 +326,18 @@ public CPU.CPUArch getArch() { return StringUtils.isBlank(arch) ? null : CPU.CPUArch.fromType(arch); } + public boolean getOnlyLeasedInstances() { + return BooleanUtils.toBoolean(onlyLeasedInstances); + } + + public Boolean getGpuEnabled() { + return gpuEnabled; + } + + public Long getExtensionId() { + return extensionId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -322,22 +357,75 @@ public void execute() { setResponseObject(response); } - protected void updateVMResponse(List response) { - for (UserVmResponse vmResponse : response) { - ResourceIcon resourceIcon = resourceIconManager.getByResourceTypeAndUuid(ResourceTag.ResourceObjectType.UserVm, vmResponse.getId()); - if (resourceIcon == null) { - ResourceTag.ResourceObjectType type = ResourceTag.ResourceObjectType.Template; - String uuid = vmResponse.getTemplateId(); - if (vmResponse.getIsoId() != null) { - uuid = vmResponse.getIsoId(); - type = ResourceTag.ResourceObjectType.ISO; - } - resourceIcon = resourceIconManager.getByResourceTypeAndUuid(type, uuid); - if (resourceIcon == null) { - continue; - } + protected Map getResourceIconsUsingOsCategory(List responses) { + Set guestOsIds = responses.stream().map(UserVmResponse::getGuestOsId).collect(Collectors.toSet()); + List guestOSList = _entityMgr.listByUuids(GuestOS.class, guestOsIds); + Map guestOSMap = guestOSList.stream() + .collect(Collectors.toMap(GuestOS::getUuid, Function.identity())); + Set guestOsCategoryIds = guestOSMap.values().stream() + .map(GuestOS::getCategoryId) + .collect(Collectors.toSet()); + Map guestOsCategoryIcons = + resourceIconManager.getByResourceTypeAndIds(ResourceTag.ResourceObjectType.GuestOsCategory, + guestOsCategoryIds); + Map vmIcons = new HashMap<>(); + for (UserVmResponse response : responses) { + GuestOS guestOS = guestOSMap.get(response.getGuestOsId()); + if (guestOS != null) { + vmIcons.put(response.getId(), guestOsCategoryIcons.get(guestOS.getCategoryId())); + } + } + return vmIcons; + } + + protected Map getResourceIconsForUsingTemplateIso(List responses) { + Map vmTemplateIsoIdMap = new HashMap<>(); + Set templateUuids = new HashSet<>(); + Set isoUuids = new HashSet<>(); + for (UserVmResponse vmResponse : responses) { + if (vmResponse.getTemplateId() != null) { + templateUuids.add(vmResponse.getTemplateId()); + vmTemplateIsoIdMap.put(vmResponse.getId(), vmResponse.getTemplateId()); + } + if (vmResponse.getIsoId() != null) { + isoUuids.add(vmResponse.getIsoId()); + vmTemplateIsoIdMap.put(vmResponse.getId(), vmResponse.getIsoId()); + } + } + Map templateOrIsoIcons = resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.Template, templateUuids); + templateOrIsoIcons.putAll(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.ISO, isoUuids)); + Map vmIcons = new HashMap<>(); + List noTemplateIsoIconResponses = new ArrayList<>(); + for (UserVmResponse response : responses) { + String uuid = vmTemplateIsoIdMap.get(response.getId()); + if (StringUtils.isNotBlank(uuid) && templateOrIsoIcons.containsKey(uuid)) { + vmIcons.put(response.getId(), + templateOrIsoIcons.get(vmTemplateIsoIdMap.get(response.getId()))); + continue; + } + noTemplateIsoIconResponses.add(response); + } + vmIcons.putAll(getResourceIconsUsingOsCategory(noTemplateIsoIconResponses)); + return vmIcons; + } + + protected void updateVMResponse(List responses) { + if (CollectionUtils.isEmpty(responses)) { + return; + } + Set vmUuids = responses.stream().map(UserVmResponse::getId).collect(Collectors.toSet()); + Map vmIcons = resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.UserVm, vmUuids); + List noVmIconResponses = responses + .stream() + .filter(r -> !vmIcons.containsKey(r.getId())) + .collect(Collectors.toList()); + vmIcons.putAll(getResourceIconsForUsingTemplateIso(noVmIconResponses)); + for (UserVmResponse vmResponse : responses) { + ResourceIcon icon = vmIcons.get(vmResponse.getId()); + if (icon == null) { + continue; } - ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(resourceIcon); + ResourceIconResponse iconResponse = _responseGenerator.createResourceIconResponse(icon); vmResponse.setResourceIconResponse(iconResponse); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java index 32756755f395..6f4431547848 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RebootVMCmd.java @@ -100,7 +100,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Rebooting User Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Rebooting User Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -115,7 +115,7 @@ public Long getApiResourceId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result; result = _userVmService.rebootVirtualMachine(this); if (result !=null){ diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java index 09c84fdbb380..f4c4d82b30d4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveIpFromVmNicCmd.java @@ -93,7 +93,7 @@ public NicSecondaryIp getIpEntry() { @Override public String getEventDescription() { - return ("Disassociating ip address with id=" + id); + return "Disassociating IP address with ID:" + getResourceUuid(ApiConstants.ID); } ///////////////////////////////////////////////////// @@ -132,7 +132,7 @@ private boolean isZoneSGEnabled() { @Override public void execute() throws InvalidParameterValueException { - CallContext.current().setEventDetails("Ip Id: " + id); + CallContext.current().setEventDetails("IP address ID: " + getResourceUuid(ApiConstants.ID)); NicSecondaryIp nicSecIp = getIpEntry(); if (nicSecIp == null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveNicFromVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveNicFromVMCmd.java index 8a891e824eea..cfbc64339909 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveNicFromVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RemoveNicFromVMCmd.java @@ -19,8 +19,6 @@ import java.util.ArrayList; import java.util.EnumSet; -import com.cloud.vm.Nic; - import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; @@ -89,7 +87,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Removing NIC " + this._uuidMgr.getUuid(Nic.class, getNicId()) + " from User Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()); + return "Removing NIC with ID: " + getResourceUuid(ApiConstants.NIC_ID) + " from User Instance: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override @@ -103,7 +101,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()) + " Nic Id: " + this._uuidMgr.getUuid(Nic.class, getNicId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " NIC ID: " + getResourceUuid(ApiConstants.NIC_ID)); UserVm result = _userVmService.removeNicFromVirtualMachine(this); ArrayList dc = new ArrayList(); dc.add(VMDetails.valueOf("nics")); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMPasswordCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMPasswordCmd.java index dbf11de83258..b6179efc0d35 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMPasswordCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMPasswordCmd.java @@ -101,7 +101,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "resetting password for Instance: " + getId(); + return "Resetting password for Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -120,11 +120,11 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacityE UserVm vm = _responseGenerator.findUserVmById(getId()); if (StringUtils.isBlank(password)) { password = _mgr.generateRandomPassword(); - logger.debug(String.format("Resetting VM [%s] password to a randomly generated password.", vm.getUuid())); + logger.debug("Resetting VM [{}] password to a randomly generated password.", vm.getUuid()); } else { - logger.debug(String.format("Resetting VM [%s] password to password defined by user.", vm.getUuid())); + logger.debug("Resetting VM [{}] password to password defined by user.", vm.getUuid()); } - CallContext.current().setEventDetails("Vm Id: " + getId()); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result = _userVmService.resetVMPassword(this, password); if (result != null){ UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", result).get(0); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java index 530677edf383..73e4ec623f23 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMSSHKeyCmd.java @@ -121,7 +121,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "resetting SSHKey for Instance: " + getId(); + return "Resetting SSH key for Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -152,7 +152,7 @@ public Long getApiResourceId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Vm Id: " + getId()); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result = _userVmService.resetVMSSHKey(this); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java index 9fb60b537c5a..8c513549506f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ResetVMUserDataCmd.java @@ -143,7 +143,7 @@ public Long getApiResourceId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException { - CallContext.current().setEventDetails("Vm Id: " + getId()); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result = _userVmService.resetVMUserData(this); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java index b92b2d1b3c16..d3459347687a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/RestoreVMCmd.java @@ -90,14 +90,14 @@ public String getEventType() { @Override public String getEventDescription() { - return "Restore an Instance to original Template or specific Snapshot"; + return "Restoring Instance with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " to original Template or specific Snapshot"; } @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException { UserVm result; - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID)); result = _userVmService.restoreVM(this); if (result != null) { UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), "virtualmachine", result).get(0); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java index cd3aeefc44b2..36d0ad9c6500 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/ScaleVMCmd.java @@ -39,7 +39,6 @@ import com.cloud.exception.ManagementServerException; import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; -import com.cloud.offering.ServiceOffering; import com.cloud.user.Account; import com.cloud.uservm.UserVm; import com.cloud.vm.VirtualMachine; @@ -148,7 +147,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Upgrading Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()) + " to service offering: " + this._uuidMgr.getUuid(ServiceOffering.class, getServiceOfferingId()); + return "Upgrading Instance with ID: " + getResourceUuid(ApiConstants.ID) + " to service offering with ID: " + getResourceUuid(ApiConstants.SERVICE_OFFERING_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java index af7058d44923..40ae91d4c264 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StartVMCmd.java @@ -161,7 +161,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Starting User Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Starting User Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -177,7 +177,7 @@ public Long getApiResourceId() { @Override public void execute() { try { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result; result = _userVmService.startVirtualMachine(this); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StopVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StopVMCmd.java index d20f27eb56db..232eeebd34be 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StopVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/StopVMCmd.java @@ -96,7 +96,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Stopping User Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getId()); + return "Stopping User Instance with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -115,7 +115,7 @@ public boolean isForced() { @Override public void execute() throws ServerApiException, ConcurrentOperationException { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); UserVm result; result = _userVmService.stopVirtualMachine(getId(), isForced()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateDefaultNicForVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateDefaultNicForVMCmd.java index 591d3871493e..011edb1a9df4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateDefaultNicForVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateDefaultNicForVMCmd.java @@ -19,8 +19,6 @@ import java.util.ArrayList; import java.util.EnumSet; -import com.cloud.vm.Nic; - import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; @@ -90,7 +88,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Updating NIC " + this._uuidMgr.getUuid(Nic.class, getNicId()) + " on User Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()); + return "Setting NIC " + getResourceUuid(ApiConstants.NIC_ID) + " as default to User Instance: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override @@ -104,7 +102,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()) + " Nic Id: " + this._uuidMgr.getUuid(Nic.class, getNicId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID) + " NIC ID: " + getResourceUuid(ApiConstants.NIC_ID)); UserVm result = _userVmService.updateDefaultNicForVirtualMachine(this); ArrayList dc = new ArrayList(); dc.add(VMDetails.valueOf("nics")); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java index 67a929bc6c26..a5f06e6ecbb3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java @@ -16,21 +16,20 @@ // under the License. package org.apache.cloudstack.api.command.user.vm; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import com.cloud.uservm.UserVm; import com.cloud.utils.StringUtils; import com.cloud.utils.exception.CloudRuntimeException; - -import org.apache.cloudstack.api.ApiArgValidator; -import org.apache.cloudstack.api.response.UserDataResponse; - +import com.cloud.utils.net.Dhcp; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiArgValidator; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.ApiErrorCode; @@ -41,15 +40,16 @@ import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.GuestOSResponse; import org.apache.cloudstack.api.response.SecurityGroupResponse; +import org.apache.cloudstack.api.response.UserDataResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.vm.lease.VMLeaseManager; +import org.apache.commons.lang3.EnumUtils; -import com.cloud.exception.InsufficientCapacityException; -import com.cloud.exception.ResourceUnavailableException; -import com.cloud.user.Account; -import com.cloud.uservm.UserVm; -import com.cloud.utils.net.Dhcp; -import com.cloud.vm.VirtualMachine; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @APICommand(name = "updateVirtualMachine", description = "Updates properties of an Instance. The Instance has to be stopped and restarted for the " + "new properties to take effect. UpdateVirtualMachine does not first check whether the Instance is stopped. " + @@ -155,6 +155,20 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements SecurityGroupAction, " autoscaling groups or CKS, delete protection will be ignored.") private Boolean deleteProtection; + @Parameter(name = ApiConstants.INSTANCE_LEASE_DURATION, type = CommandType.INTEGER, since = "4.21.0", + description = "Number of days to lease the instance from now onward. Use -1 to remove the existing lease") + private Integer leaseDuration; + + @Parameter(name = ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION, type = CommandType.STRING, since = "4.21.0", + description = "Lease expiry action, valid values are STOP and DESTROY") + private String leaseExpiryAction; + + @Parameter(name = ApiConstants.CLEAN_UP_EXTRA_CONFIG, type = CommandType.BOOLEAN, since = "4.23.0", + description = "Optional boolean field, which indicates if extraconfig for the instance should be " + + "cleaned up or not (If set to true, extraconfig removed for this instance, extraconfig field " + + "ignored; if false or not set, no action)") + private Boolean cleanupExtraConfig; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -266,14 +280,26 @@ public String getExtraConfig() { return extraConfig; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// - public Long getOsTypeId() { return osTypeId; } + public boolean isCleanupExtraConfig() { + return Boolean.TRUE.equals(cleanupExtraConfig); + } + + public void setId(Long id) { + this.id = id; + } + + public void setSecurityGroupIdList(List securityGroupIdList) { + this.securityGroupIdList = securityGroupIdList; + } + + ///////////////////////////////////////////////////// + /////////////// API Implementation/////////////////// + ///////////////////////////////////////////////////// + @Override public String getCommandName() { return s_name; @@ -295,7 +321,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException { - CallContext.current().setEventDetails("Instance Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + ApiConstants.ID); UserVm result = null; try { result = _userVmService.updateVirtualMachine(this); @@ -328,4 +354,21 @@ public Long getApiResourceId() { public ApiCommandResourceType getApiResourceType() { return ApiCommandResourceType.VirtualMachine; } + + public Integer getLeaseDuration() { + return leaseDuration; + } + + public VMLeaseManager.ExpiryAction getLeaseExpiryAction() { + if (StringUtils.isBlank(leaseExpiryAction)) { + return null; + } + VMLeaseManager.ExpiryAction action = EnumUtils.getEnumIgnoreCase(VMLeaseManager.ExpiryAction.class, leaseExpiryAction); + if (action == null) { + throw new InvalidParameterValueException("Invalid value configured for leaseexpiryaction, valid values are: " + + com.cloud.utils.EnumUtils.listValues(VMLeaseManager.ExpiryAction.values())); + } + return action; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java index b7222944fe07..9b5b05373102 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmd.java @@ -22,22 +22,25 @@ import com.cloud.vm.VirtualMachine; 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.BaseCmd; import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceSchedule; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import javax.inject.Inject; import java.util.Date; +@Deprecated @APICommand(name = "updateVMSchedule", description = "Update Instance Schedule.", responseObject = VMScheduleResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, since = "4.19.0", authorized = {RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User}) public class UpdateVMScheduleCmd extends BaseCmd { @Inject - VMScheduleManager vmScheduleManager; + ResourceScheduleManager resourceScheduleManager; @Parameter(name = ApiConstants.ID, type = CommandType.UUID, @@ -68,14 +71,14 @@ public class UpdateVMScheduleCmd extends BaseCmd { type = CommandType.DATE, required = false, description = "Start date from which the schedule becomes active" - + "Use format \"yyyy-MM-dd hh:mm:ss\")") + + "(Format \"yyyy-MM-dd hh:mm:ss\")") private Date startDate; @Parameter(name = ApiConstants.END_DATE, type = CommandType.DATE, required = false, description = "End date after which the schedule becomes inactive" - + "Use format \"yyyy-MM-dd hh:mm:ss\")") + + "(Format \"yyyy-MM-dd hh:mm:ss\")") private Date endDate; @Parameter(name = ApiConstants.ENABLED, @@ -84,9 +87,9 @@ public class UpdateVMScheduleCmd extends BaseCmd { description = "Enable Instance schedule") private Boolean enabled; - ///////////////////////////////////////////////////// - /////////////////// Accessors /////////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////////// Accessors /////////////////////// + /// ////////////////////////////////////////////////// public Long getId() { return id; @@ -116,24 +119,29 @@ public Boolean getEnabled() { return enabled; } - ///////////////////////////////////////////////////// - /////////////// API Implementation/////////////////// - ///////////////////////////////////////////////////// + /// ////////////////////////////////////////////////// + /// //////////// API Implementation/////////////////// + /// ////////////////////////////////////////////////// @Override public void execute() { - VMScheduleResponse response = vmScheduleManager.updateSchedule(this); + ResourceScheduleResponse scheduleResponse = resourceScheduleManager.updateSchedule( + getId(), getDescription(), getSchedule(), getTimeZone(), getStartDate(), getEndDate(), getEnabled(), null); + VMScheduleResponse response = new VMScheduleResponse(scheduleResponse); response.setResponseName(getCommandName()); setResponseObject(response); } @Override public long getEntityOwnerId() { - VMSchedule vmSchedule = _entityMgr.findById(VMSchedule.class, getId()); - if (vmSchedule == null) { - throw new InvalidParameterValueException(String.format("Unable to find vmSchedule by id=%d", getId())); + ResourceSchedule schedule = _entityMgr.findById(ResourceSchedule.class, getId()); + if (schedule == null || !ApiCommandResourceType.VirtualMachine.equals(schedule.getResourceType())) { + throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId())); + } + VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, schedule.getResourceId()); + if (vm == null) { + throw new InvalidParameterValueException(String.format("Unable to find VM schedule by id=%d", getId())); } - VirtualMachine vm = _entityMgr.findById(VirtualMachine.class, vmSchedule.getVmId()); return vm.getAccountId(); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicCmd.java new file mode 100644 index 000000000000..363273a4670d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicCmd.java @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.vm; + +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ACL; +import org.apache.cloudstack.api.APICommand; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.BaseAsyncCmd; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.api.ResponseObject; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.NicResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.apache.cloudstack.context.CallContext; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; +import com.cloud.uservm.UserVm; + +import java.util.ArrayList; +import java.util.EnumSet; + +@APICommand(name = "updateVmNic", description = "Updates the specified VM NIC", responseObject = NicResponse.class, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false, + authorized = { RoleType.Admin, RoleType.ResourceAdmin, RoleType.DomainAdmin, RoleType.User }) +public class UpdateVmNicCmd extends BaseAsyncCmd { + + @ACL + @Parameter(name = ApiConstants.NIC_ID, type = CommandType.UUID, entityType = NicResponse.class, required = true, description = "NIC ID") + private Long nicId; + + @Parameter(name = ApiConstants.ENABLED, type = CommandType.BOOLEAN, description = "If true, sets the NIC state to UP; otherwise, sets the NIC state to DOWN") + private Boolean enabled; + + public Long getNicId() { + return nicId; + } + + public Boolean isEnabled() { + return enabled; + } + + @Override + public String getEventType() { + return EventTypes.EVENT_NIC_UPDATE; + } + + @Override + public String getEventDescription() { + return String.format("Updating NIC %s.", getResourceUuid(ApiConstants.NIC_ID)); + } + + @Override + public long getEntityOwnerId() { + UserVm vm = _responseGenerator.findUserVmByNicId(nicId); + if (vm == null) { + return Account.ACCOUNT_ID_SYSTEM; + } + return vm.getAccountId(); + } + + @Override + public void execute() { + CallContext.current().setEventDetails(String.format("NIC ID: %s", getResourceUuid(ApiConstants.NIC_ID))); + + UserVm result = _userVmService.updateVirtualMachineNic(this); + + ArrayList dc = new ArrayList<>(); + dc.add(ApiConstants.VMDetails.valueOf("nics")); + EnumSet details = EnumSet.copyOf(dc); + + if (result != null){ + UserVmResponse response = _responseGenerator.createUserVmResponse(ResponseObject.ResponseView.Restricted, "virtualmachine", details, result).get(0); + response.setResponseName(getCommandName()); + this.setResponseObject(response); + } else { + throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to update NIC from VM."); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicIpCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicIpCmd.java index f8ca0b7afea0..6da34c7ef0b0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicIpCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVmNicIpCmd.java @@ -53,7 +53,7 @@ public class UpdateVmNicIpCmd extends BaseAsyncCmd { //////////////// API parameters ///////////////////// ///////////////////////////////////////////////////// @Parameter(name=ApiConstants.NIC_ID, type=CommandType.UUID, entityType = NicResponse.class, required = true, - description = "The ID of the NIC to which you want to assign private IP") + description = "The ID of the NIC to which you want to assign private IP") private Long nicId; @Parameter(name = ApiConstants.IP_ADDRESS, type = CommandType.STRING, required = false, @@ -123,7 +123,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Associating IP to NIC id: " + this._uuidMgr.getUuid(Network.class, getNetworkId()) + " in zone " + this._uuidMgr.getUuid(DataCenter.class, getZoneId()); + return "Associating IP to NIC with ID: " + getResourceUuid(ApiConstants.NIC_ID) + " in zone " + this._uuidMgr.getUuid(DataCenter.class, getZoneId()); } ///////////////////////////////////////////////////// @@ -139,7 +139,7 @@ public static String getResultObjectName() { public void execute() throws ResourceUnavailableException, ResourceAllocationException, ConcurrentOperationException, InsufficientCapacityException { - CallContext.current().setEventDetails("Nic Id: " + getNicId() ); + CallContext.current().setEventDetails("NIC ID: " + getResourceUuid(ApiConstants.NIC_ID)); String ip; if ((ip = getIpaddress()) != null) { if (!NetUtils.isValidIp4(ip)) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java index c45a18d2fa2b..83908802a690 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpgradeVMCmd.java @@ -139,7 +139,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceAllocationException { - CallContext.current().setEventDetails("Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.ID)); ServiceOffering serviceOffering = _entityMgr.findById(ServiceOffering.class, serviceOfferingId); if (serviceOffering == null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/CreateVMGroupCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/CreateVMGroupCmd.java index a142ffa2d492..12f534e1a221 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/CreateVMGroupCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vmgroup/CreateVMGroupCmd.java @@ -82,7 +82,7 @@ public Long getProjectId() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java index 6e1a7daf4c23..d3128599b616 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/CreateVMSnapshotCmd.java @@ -34,7 +34,6 @@ import com.cloud.event.EventTypes; import com.cloud.exception.ResourceAllocationException; import com.cloud.uservm.UserVm; -import com.cloud.vm.VirtualMachine; import com.cloud.vm.snapshot.VMSnapshot; @APICommand(name = "createVMSnapshot", description = "Creates Snapshot for an Instance.", responseObject = VMSnapshotResponse.class, since = "4.2.0", entityType = {VMSnapshot.class}, @@ -105,7 +104,7 @@ public void create() throws ResourceAllocationException { @Override public String getEventDescription() { - return "Creating Snapshot for Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId()); + return "Creating Snapshot for Instance: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override @@ -115,7 +114,7 @@ public String getEventType() { @Override public void execute() { - CallContext.current().setEventDetails("VM Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVmId())); + CallContext.current().setEventDetails("Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID)); VMSnapshot result = _vmSnapshotService.createVMSnapshot(getVmId(), getEntityId(), getQuiescevm()); if (result != null) { VMSnapshotResponse response = _responseGenerator.createVMSnapshotResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java index afd63e8e64be..3373ac534cc8 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/DeleteVMSnapshotCmd.java @@ -62,7 +62,7 @@ public long getEntityOwnerId() { @Override public void execute() { - CallContext.current().setEventDetails("vmsnapshot id: " + this._uuidMgr.getUuid(VMSnapshot.class, getId())); + CallContext.current().setEventDetails("Instance Snapshot ID: " + getResourceUuid(ApiConstants.VM_SNAPSHOT_ID)); boolean result = _vmSnapshotService.deleteVMSnapshot(getId()); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); @@ -74,7 +74,7 @@ public void execute() { @Override public String getEventDescription() { - return "Delete Instance Snapshot: " + this._uuidMgr.getUuid(VMSnapshot.class, getId()); + return "Deleting Instance Snapshot with ID: " + getResourceUuid(ApiConstants.VM_SNAPSHOT_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java index 43f20362e98d..d44cefca5027 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vmsnapshot/RevertToVMSnapshotCmd.java @@ -74,7 +74,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, ConcurrentOperationException { - CallContext.current().setEventDetails("vmsnapshot id: " + this._uuidMgr.getUuid(VMSnapshot.class, getVmSnapShotId())); + CallContext.current().setEventDetails("Instance Snapshot ID: " + getResourceUuid(ApiConstants.VM_SNAPSHOT_ID)); UserVm result = _vmSnapshotService.revertToSnapshot(getVmSnapShotId()); if (result != null) { UserVmResponse response = _responseGenerator.createUserVmResponse(getResponseView(), @@ -88,7 +88,7 @@ public void execute() throws ResourceUnavailableException, InsufficientCapacity @Override public String getEventDescription() { - return "Revert from Instance Snapshot: " + this._uuidMgr.getUuid(VMSnapshot.class, getVmSnapShotId()); + return "Reverting from Instance Snapshot with ID: " + getResourceUuid(ApiConstants.VM_SNAPSHOT_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java index b1e58bb6ef63..9e1c1056fdf9 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AddResourceDetailCmd.java @@ -90,7 +90,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "adding details to the resource "; + return "Adding details to the resource "; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AssignVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AssignVolumeCmd.java index f39853512281..f50abaf73c96 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AssignVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AssignVolumeCmd.java @@ -70,6 +70,21 @@ public Long getProjectid() { return projectid; } + ///////////////////////////////////////////////////// + /////////////////// Setter/////////////////////////// + ///////////////////////////////////////////////////// + public void setVolumeId(Long volumeId) { + this.volumeId = volumeId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public void setProjectId(Long projectid) { + this.projectid = projectid; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AttachVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AttachVolumeCmd.java index 23fbc0fa0c75..8624043afc51 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AttachVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/AttachVolumeCmd.java @@ -111,12 +111,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "Attaching volume: " + this._uuidMgr.getUuid(Volume.class, getId()) + " to Instance: " + this._uuidMgr.getUuid(VirtualMachine.class, getVirtualMachineId()); + return "Attaching volume with ID: " + getResourceUuid(ApiConstants.ID) + " to Instance with ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } @Override public void execute() { - CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()) + " Instance Id: " + this._uuidMgr.getUuid(VirtualMachine.class, getVirtualMachineId())); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID) + " Instance ID: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID)); Volume result = _volumeService.attachVolumeToVM(this); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(getResponseView(), result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java index 77c30aa6be92..c8cda7e1c19c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ChangeOfferingForVolumeCmd.java @@ -22,7 +22,6 @@ import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.ResourceUnavailableException; -import com.cloud.offering.DiskOffering; import com.cloud.storage.Volume; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.APICommand; @@ -130,12 +129,12 @@ public String getEventType() { @Override public String getEventDescription() { - return "Changing Disk offering of Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId()) + " to " + this._uuidMgr.getUuid(DiskOffering.class, getNewDiskOfferingId()); + return "Changing disk offering of volume with ID: " + getResourceUuid(ApiConstants.ID) + " to " + getResourceUuid(ApiConstants.DISK_OFFERING_ID); } @Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { - CallContext.current().setEventDetails("Volume Id: " + getId()); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); Volume result = _volumeService.changeDiskOfferingForVolume(this); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseObject.ResponseView.Restricted, result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java index 56fdf6bc126c..fdbd4a61c072 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CheckAndRepairVolumeCmd.java @@ -105,7 +105,7 @@ public String getEventType() { @Override public String getEventDescription() { - return String.format("check and repair operation on volume: %s", this._uuidMgr.getUuid(Volume.class, getId())); + return "Starting checking and repairing operation on volume: " + getResourceUuid(ApiConstants.ID); } @Override @@ -120,7 +120,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() throws ResourceAllocationException { - CallContext.current().setEventDetails("Volume Id: " + getId()); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); Pair result = _volumeService.checkAndRepairVolume(this); Volume volume = _responseGenerator.findVolumeById(getId()); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java index 27b592aa8f15..538e263ae9de 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/CreateVolumeCmd.java @@ -30,8 +30,10 @@ import org.apache.cloudstack.api.command.user.UserCmd; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.DomainResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; import org.apache.cloudstack.api.response.ProjectResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.UserVmResponse; import org.apache.cloudstack.api.response.VolumeResponse; import org.apache.cloudstack.api.response.ZoneResponse; @@ -109,6 +111,21 @@ public class CreateVolumeCmd extends BaseAsyncCreateCustomIdCmd implements UserC description = "The ID of the Instance; to be used with snapshot Id, Instance to which the volume gets attached after creation") private Long virtualMachineId; + @Parameter(name = ApiConstants.KMS_KEY_ID, + type = CommandType.UUID, + entityType = KMSKeyResponse.class, + description = "ID of the KMS Key for volume encryption (required if encryption enabled for zone)", + since = "4.23.0") + private Long kmsKeyId; + + @Parameter(name = ApiConstants.STORAGE_ID, + type = CommandType.UUID, + entityType = StoragePoolResponse.class, + description = "Storage pool ID to create the volume in. Cannot be used with the snapshotid parameter.", + authorized = {RoleType.Admin}, + since = "4.22.1") + private Long storageId; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -142,6 +159,10 @@ public Long getMaxIops() { } public Long getSnapshotId() { + if (storageId != null && snapshotId != null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, + "Snapshot ID cannot be specified with the Storage ID."); + } return snapshotId; } @@ -153,6 +174,14 @@ private Long getProjectId() { return projectId; } + public Long getStorageId() { + if (snapshotId != null && storageId != null) { + throw new ServerApiException(ApiErrorCode.PARAM_ERROR, + "Storage ID cannot be specified with the Snapshot ID."); + } + return storageId; + } + public Boolean getDisplayVolume() { return displayVolume; } @@ -169,6 +198,10 @@ public Long getVirtualMachineId() { return virtualMachineId; } + public Long getKmsKeyId() { + return kmsKeyId; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -188,7 +221,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } @@ -203,7 +236,17 @@ public String getEventType() { @Override public String getEventDescription() { - return "Creating volume: " + getVolumeName() + ((getSnapshotId() == null) ? "" : " from Snapshot: " + this._uuidMgr.getUuid(Snapshot.class, getSnapshotId())); + String description = "Creating volume "; + + if (getVolumeName() != null) { + description += getVolumeName(); + } + + if (getSnapshotId() != null) { + description += " from Snapshot: " + getResourceUuid(ApiConstants.SNAPSHOT_ID); + } + + return description; } @Override @@ -220,7 +263,7 @@ public void create() throws ResourceAllocationException { @Override public void execute() { - CallContext.current().setEventDetails("Volume Id: " + getEntityUuid() + ((getSnapshotId() == null) ? "" : " from Snapshot: " + this._uuidMgr.getUuid(Snapshot.class, getSnapshotId()))); + CallContext.current().setEventDetails("Volume ID: " + getEntityUuid() + ((getSnapshotId() == null) ? "" : " from Snapshot with ID: " + getResourceUuid(ApiConstants.SNAPSHOT_ID))); Volume volume = _volumeService.createVolume(this); if (volume != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(getResponseView(), volume); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DeleteVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DeleteVolumeCmd.java index e21103654c81..ab6cda651b06 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DeleteVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DeleteVolumeCmd.java @@ -84,8 +84,8 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() throws ConcurrentOperationException { - CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId())); - Volume result = _volumeService.destroyVolume(id, CallContext.current().getCallingAccount(), true, false); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); + Volume result = _volumeService.destroyVolume(id, CallContext.current().getCallingAccount(), true, false, null); if (result != null) { SuccessResponse response = new SuccessResponse(getCommandName()); setResponseObject(response); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DestroyVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DestroyVolumeCmd.java index 32ddec880861..e9e388436642 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DestroyVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DestroyVolumeCmd.java @@ -100,7 +100,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "destroying volume: " + getId(); + return "Destroying volume with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -115,8 +115,8 @@ public Long getApiResourceId() { @Override public void execute() { - CallContext.current().setEventDetails("Volume Id: " + getId()); - Volume result = _volumeService.destroyVolume(getId(), CallContext.current().getCallingAccount(), getExpunge(), false); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); + Volume result = _volumeService.destroyVolume(getId(), CallContext.current().getCallingAccount(), getExpunge(), false, null); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Restricted, result); response.setResponseName(getCommandName()); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java index 9c8b8fcf6e67..f6e811da6055 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/DetachVolumeCmd.java @@ -38,7 +38,7 @@ import com.cloud.uservm.UserVm; import com.cloud.vm.VirtualMachine; -@APICommand(name = "detachVolume", description = "Detaches a disk volume from an Instance.", responseObject = VolumeResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class}, +@APICommand(name = "detachVolume", description = "Detaches a disk volume from an Instance.", responseObject = VolumeResponse.class, responseView = ResponseView.Restricted, entityType = {VirtualMachine.class}, requestHasSensitiveInfo = false, responseHasSensitiveInfo = false) public class DetachVolumeCmd extends BaseAsyncCmd implements UserCmd { private static final String s_name = "detachvolumeresponse"; @@ -77,6 +77,10 @@ public Long getVirtualMachineId() { return virtualMachineId; } + public void setId(Long id) { + this.id = id; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -126,15 +130,19 @@ public String getEventType() { @Override public String getEventDescription() { - StringBuilder sb = new StringBuilder(); + String description = "Detaching volume"; + if (id != null) { - sb.append(": " + this._uuidMgr.getUuid(Volume.class, id)); - } else if ((deviceId != null) && (virtualMachineId != null)) { - sb.append(" with device id: " + deviceId + " from Instance: " + ((getVirtualMachineId() != null) ? this._uuidMgr.getUuid(VirtualMachine.class, getVirtualMachineId()) : "" )); + description += ": " + getResourceUuid(ApiConstants.ID); + } + + if ((deviceId != null) && (virtualMachineId != null)) { + description += " with device id: " + deviceId + " from Instance: " + getResourceUuid(ApiConstants.VIRTUAL_MACHINE_ID); } else { - sb.append(" "); + description += " "; } - return "detaching volume" + sb.toString(); + + return description; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java index 2b225f4fd347..f50d23a6b60c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ExtractVolumeCmd.java @@ -16,8 +16,6 @@ // under the License. package org.apache.cloudstack.api.command.user.volume; - -import com.cloud.dc.DataCenter; import org.apache.cloudstack.acl.SecurityChecker.AccessType; import org.apache.cloudstack.api.ACL; import org.apache.cloudstack.api.APICommand; @@ -115,10 +113,7 @@ public String getEventType() { @Override public String getEventDescription() { - String volumeId = this._uuidMgr.getUuid(Volume.class, getId()); - String zoneId = this._uuidMgr.getUuid(DataCenter.class, getZoneId()); - - return String.format("Extracting volume: %s from zone: %s", volumeId, zoneId); + return "Extracting volume with ID: " + getResourceUuid(ApiConstants.ID) + " from zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/GetUploadParamsForVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/GetUploadParamsForVolumeCmd.java index 0020fe4021e8..1e3b2e460772 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/GetUploadParamsForVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/GetUploadParamsForVolumeCmd.java @@ -72,7 +72,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(getAccountName(), getDomainId(), getProjectId(), true); + Long accountId = _accountService.finalizeAccountId(getAccountName(), getDomainId(), getProjectId(), true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java index a4cd299dae9c..88f87e941e78 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ListVolumesCmd.java @@ -29,6 +29,7 @@ import org.apache.cloudstack.api.response.ClusterResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; import org.apache.cloudstack.api.response.HostResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.PodResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; @@ -90,6 +91,9 @@ public class ListVolumesCmd extends BaseListRetrieveOnlyResourceCountCmd impleme @Parameter(name = ApiConstants.DISK_OFFERING_ID, type = CommandType.UUID, entityType = DiskOfferingResponse.class, description = "List volumes by disk offering", since = "4.4") private Long diskOfferingId; + @Parameter(name = ApiConstants.KMS_KEY_ID, type = CommandType.UUID, entityType = KMSKeyResponse.class, description = "List volumes by KMS Key", since = "4.23") + private Long kmsKeyId; + @Parameter(name = ApiConstants.DISPLAY_VOLUME, type = CommandType.BOOLEAN, description = "List resources by display flag; only ROOT admin is eligible to pass this parameter", since = "4.4", authorized = { RoleType.Admin}) private Boolean display; @@ -136,6 +140,10 @@ public Long getDiskOfferingId() { return diskOfferingId; } + public Long getKmsKeyId() { + return kmsKeyId; + } + public String getType() { return type; } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java index ea6890ac3e82..9927978ad55e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/MigrateVolumeCmd.java @@ -30,7 +30,6 @@ import org.apache.cloudstack.api.response.VolumeResponse; import com.cloud.event.EventTypes; -import com.cloud.storage.StoragePool; import com.cloud.storage.Volume; import com.cloud.user.Account; @@ -110,7 +109,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Attempting to migrate volume Id: " + this._uuidMgr.getUuid(Volume.class, getVolumeId()) + " to storage pool Id: " + this._uuidMgr.getUuid(StoragePool.class, getStoragePoolId()); + return "Attempting to migrate volume with ID: " + getResourceUuid(ApiConstants.VOLUME_ID) + " to storage pool: " + getResourceUuid(ApiConstants.STORAGE_ID); } public Long getNewDiskOfferingId() { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RecoverVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RecoverVolumeCmd.java index cd5a7735e382..4d6be7270afb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RecoverVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/RecoverVolumeCmd.java @@ -87,7 +87,7 @@ public ApiCommandResourceType getApiResourceType() { @Override public void execute() { - CallContext.current().setEventDetails("Volume Id: " + getId()); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); Volume result = _volumeService.recoverVolume(getId()); if (result != null) { VolumeResponse response = _responseGenerator.createVolumeResponse(ResponseView.Full, result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java index 5b2b6709cf64..f8f744285c04 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/ResizeVolumeCmd.java @@ -190,11 +190,13 @@ public String getEventType() { @Override public String getEventDescription() { + String baseDescription = "Resizing volume with ID: " + getResourceUuid(ApiConstants.ID); + if (getSize() != null) { - return "Volume Id: " + this._uuidMgr.getUuid(Volume.class, getEntityId()) + " to size " + getSize() + " GB"; - } else { - return "Volume Id: " + this._uuidMgr.getUuid(Volume.class, getEntityId()); + baseDescription = baseDescription + " to size " + getSize() + " GB."; } + + return baseDescription; } @Override @@ -202,9 +204,9 @@ public void execute() { Volume volume = null; try { if (size != null) { - CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getEntityId()) + " to size " + getSize() + " GB"); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID) + " to size " + getSize() + " GB"); } else { - CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getEntityId())); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); } volume = _volumeService.resizeVolume(this); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java index 0d3fc59a5285..00c50fa5ffff 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UpdateVolumeCmd.java @@ -178,7 +178,7 @@ public String getEventDescription() { @Override public void execute() { - CallContext.current().setEventDetails("Volume Id: " + this._uuidMgr.getUuid(Volume.class, getId())); + CallContext.current().setEventDetails("Volume ID: " + getResourceUuid(ApiConstants.ID)); Volume result = _volumeService.updateVolume(getId(), getPath(), getState(), getStorageId(), getDisplayVolume(), getDeleteProtection(), getCustomId(), getEntityOwnerId(), getChainInfo(), getName()); if (result != null) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java index 3d23a6317223..33a9251e094f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/volume/UploadVolumeCmd.java @@ -32,7 +32,6 @@ import org.apache.cloudstack.api.response.ZoneResponse; import org.apache.cloudstack.context.CallContext; -import com.cloud.dc.DataCenter; import com.cloud.event.EventTypes; import com.cloud.exception.ConcurrentOperationException; import com.cloud.exception.InsufficientCapacityException; @@ -159,7 +158,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } @@ -169,7 +168,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "uploading volume: " + getVolumeName() + " in the zone " + this._uuidMgr.getUuid(DataCenter.class, getZoneId()); + return "Uploading volume: " + getVolumeName() + " to zone with ID: " + getResourceUuid(ApiConstants.ZONE_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java index 819aae18794d..9b5d25aa0ddb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/CreateStaticRouteCmd.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.PrivateGatewayResponse; import org.apache.cloudstack.api.response.StaticRouteResponse; +import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.context.CallContext; import com.cloud.event.EventTypes; @@ -45,20 +46,40 @@ public class CreateStaticRouteCmd extends BaseAsyncCreateCmd { @Parameter(name = ApiConstants.GATEWAY_ID, type = CommandType.UUID, entityType = PrivateGatewayResponse.class, - required = true, - description = "The gateway ID we are creating static route for") + description = "The gateway ID we are creating static route for. Mutually exclusive with the nexthop parameter") private Long gatewayId; + @Parameter(name = ApiConstants.VPC_ID, + type = CommandType.UUID, + entityType = VpcResponse.class, + description = "the vpc id for which the static route is created. This is required for nexthop parameter", + since = "4.21.0") + private Long vpcId; + + @Parameter(name = ApiConstants.NEXT_HOP, + type = CommandType.STRING, + description = "the next hop of static route. Mutually exclusive with the gatewayid parameter", + since = "4.21.0") + private String nextHop; + @Parameter(name = ApiConstants.CIDR, required = true, type = CommandType.STRING, description = "Static route CIDR") private String cidr; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// - public long getGatewayId() { + public Long getGatewayId() { return gatewayId; } + public Long getVpcId() { + return vpcId; + } + + public String getNextHop() { + return nextHop; + } + public String getCidr() { return cidr; } @@ -69,11 +90,11 @@ public String getCidr() { @Override public void create() throws ResourceAllocationException { try { - StaticRoute result = _vpcService.createStaticRoute(getGatewayId(), getCidr()); + StaticRoute result = _vpcService.createStaticRoute(getGatewayId(), getVpcId(), getNextHop(), getCidr()); setEntityId(result.getId()); setEntityUuid(result.getUuid()); } catch (NetworkRuleConflictException ex) { - logger.info("Network rule conflict: " + ex.getMessage()); + logger.info("Network rule conflict: {}", ex.getMessage()); logger.trace("Network rule conflict: ", ex); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, ex.getMessage()); } @@ -94,7 +115,7 @@ public void execute() throws ResourceUnavailableException { boolean success = false; StaticRoute route = null; try { - CallContext.current().setEventDetails("Static route Id: " + getEntityId()); + CallContext.current().setEventDetails("Static route ID: " + getEntityUuid()); success = _vpcService.applyStaticRoute(getEntityId()); // State is different after the route is applied, so retrieve the object only here route = _entityMgr.findById(StaticRoute.class, getEntityId()); @@ -114,11 +135,8 @@ public void execute() throws ResourceUnavailableException { @Override public long getEntityOwnerId() { - VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId); - if (gateway == null) { - throw new InvalidParameterValueException("Invalid gateway id is specified"); - } - return _entityMgr.findById(Vpc.class, gateway.getVpcId()).getAccountId(); + Long vpcId = getSyncObjId(); + return _entityMgr.findById(Vpc.class, vpcId).getAccountId(); } @Override @@ -128,11 +146,20 @@ public String getSyncObjType() { @Override public Long getSyncObjId() { - VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId); - if (gateway == null) { - throw new InvalidParameterValueException("Invalid id is specified for the gateway"); + if (gatewayId != null) { + VpcGateway gateway = _entityMgr.findById(VpcGateway.class, gatewayId); + if (gateway == null) { + throw new InvalidParameterValueException("Invalid id is specified for the gateway"); + } + return gateway.getVpcId(); + } else if (vpcId != null) { + Vpc vpc = _entityMgr.findById(Vpc.class, vpcId); + if (vpc == null) { + throw new InvalidParameterValueException("Invalid vpc id is specified"); + } + return vpc.getId(); } - return gateway.getVpcId(); + throw new InvalidParameterValueException("One of vpcId or gatewayId must be specified"); } @Override 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 8b45e1b501ab..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 @@ -16,6 +16,7 @@ // under the License. package org.apache.cloudstack.api.command.user.vpc; +import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.StringUtils; import org.apache.cloudstack.acl.RoleType; @@ -125,6 +126,15 @@ public class CreateVPCCmd extends BaseAsyncCreateCmd implements UserCmd { @Parameter(name=ApiConstants.AS_NUMBER, type=CommandType.LONG, since = "4.20.0", description="the AS Number of the VPC tiers") private Long asNumber; + @Parameter(name=ApiConstants.USE_VIRTUAL_ROUTER_IP_RESOLVER, type=CommandType.BOOLEAN, + 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 /////////////////////// // /////////////////////////////////////////////////// @@ -205,6 +215,14 @@ public Long getAsNumber() { return asNumber; } + public boolean getUseVrIpResolver() { + return BooleanUtils.toBoolean(useVrIpResolver); + } + + public boolean getKeepMacAddressOnPublicNic() { + return keepMacAddressOnPublicNic; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// @@ -264,7 +282,7 @@ public String getCommandName() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteStaticRouteCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteStaticRouteCmd.java index 532a4108076c..cf1805973b77 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteStaticRouteCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteStaticRouteCmd.java @@ -69,7 +69,7 @@ public String getEventType() { @Override public String getEventDescription() { - return ("Deleting static route id=" + id); + return "Deleting static route with ID: " + getResourceUuid(ApiConstants.ID); } @Override @@ -87,7 +87,7 @@ public long getEntityOwnerId() { @Override public void execute() throws ResourceUnavailableException { - CallContext.current().setEventDetails("Route Id: " + id); + CallContext.current().setEventDetails("Route ID: " + getResourceUuid(ApiConstants.ID)); boolean result = _vpcService.revokeStaticRoute(id); if (result) { diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteVPCCmd.java index ccaac2b1e29b..e42b4761ea8f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/DeleteVPCCmd.java @@ -65,7 +65,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "Deleting VPC id=" + getId(); + return "Deleting VPC with ID: " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java index 9dadf061753a..f6a408a66a3d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpc/RestartVPCCmd.java @@ -118,7 +118,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "restarting VPC id=" + getId(); + return "Restarting VPC with ID: " + getResourceUuid(ApiConstants.ID); } @Override 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 88e38649802b..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/////////////////// ///////////////////////////////////////////////////// @@ -143,7 +152,7 @@ public String getEventType() { @Override public String getEventDescription() { - return "updating VPC id=" + getId(); + return "Updating VPC " + getResourceUuid(ApiConstants.ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java index 6910ce5b3710..78cd9a3ac7e4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/AddVpnUserCmd.java @@ -28,7 +28,6 @@ import org.apache.cloudstack.api.response.VpnUsersResponse; import org.apache.cloudstack.context.CallContext; -import com.cloud.domain.Domain; import com.cloud.event.EventTypes; import com.cloud.network.VpnUser; import com.cloud.user.Account; @@ -89,7 +88,7 @@ public Long getProjectId() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } @@ -110,7 +109,6 @@ public String getEventType() { @Override public void execute() { VpnUser vpnUser = _entityMgr.findById(VpnUser.class, getEntityId()); - Account account = _entityMgr.findById(Account.class, vpnUser.getAccountId()); try { if (!_ravService.applyVpnUsers(vpnUser.getAccountId(), userName)) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to add VPN User"); @@ -118,24 +116,10 @@ public void execute() { } catch (Exception ex) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, ex.getMessage()); } - - VpnUsersResponse vpnResponse = new VpnUsersResponse(); - vpnResponse.setId(vpnUser.getUuid()); - vpnResponse.setUserName(vpnUser.getUsername()); - vpnResponse.setAccountName(account.getAccountName()); // re-retrieve the vpnuser, as the call to `applyVpnUsers` might have changed the state vpnUser = _entityMgr.findById(VpnUser.class, getEntityId()); - vpnResponse.setState(vpnUser.getState().toString()); - - Domain domain = _entityMgr.findById(Domain.class, account.getDomainId()); - if (domain != null) { - vpnResponse.setDomainId(domain.getUuid()); - vpnResponse.setDomainName(domain.getName()); - vpnResponse.setDomainPath(domain.getPath()); - } - + VpnUsersResponse vpnResponse = _responseGenerator.createVpnUserResponse(vpnUser); vpnResponse.setResponseName(getCommandName()); - vpnResponse.setObjectName("vpnuser"); setResponseObject(vpnResponse); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java index e52e3008202c..c730c2c5fe88 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateRemoteAccessVpnCmd.java @@ -146,7 +146,7 @@ public void create() { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create remote access VPN"); } } catch (NetworkRuleConflictException e) { - logger.info("Network rule conflict: " + e.getMessage()); + logger.info("Network rule conflict: {}", e.getMessage()); logger.trace("Network Rule Conflict: ", e); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, e.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnConnectionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnConnectionCmd.java index e7b3863e7e32..3d6b7918effd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnConnectionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnConnectionCmd.java @@ -133,7 +133,7 @@ public void create() { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, "Failed to create site to site VPN connection"); } } catch (NetworkRuleConflictException e) { - logger.info("Network rule conflict: " + e.getMessage()); + logger.info("Network rule conflict: {}", e.getMessage()); logger.trace("Network Rule Conflict: ", e); throw new ServerApiException(ApiErrorCode.NETWORK_RULE_CONFLICT_ERROR, e.getMessage()); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnCustomerGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnCustomerGatewayCmd.java index ef0e2354495f..0da813eb4867 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnCustomerGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnCustomerGatewayCmd.java @@ -167,7 +167,7 @@ public String getIkeVersion() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { accountId = CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java index 13a19613c1b8..665f699c4ef3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/CreateVpnGatewayCmd.java @@ -28,6 +28,7 @@ import org.apache.cloudstack.api.BaseAsyncCreateCmd; import org.apache.cloudstack.api.Parameter; import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.IPAddressResponse; import org.apache.cloudstack.api.response.Site2SiteVpnGatewayResponse; import org.apache.cloudstack.api.response.VpcResponse; import org.apache.cloudstack.context.CallContext; @@ -47,6 +48,13 @@ public class CreateVpnGatewayCmd extends BaseAsyncCreateCmd { description = "Public IP address id of the VPN gateway") private Long vpcId; + @Parameter(name = ApiConstants.IP_ADDRESS_ID, + type = CommandType.UUID, + entityType = IPAddressResponse.class, + description = "the public IP address ID for which VPN gateway is being enabled. By default the source NAT IP or router IP will be used.", + since = "4.21.0") + private Long ipAddressId; + @Parameter(name = ApiConstants.FOR_DISPLAY, type = CommandType.BOOLEAN, description = "An optional field, whether to the display the VPN to the end User or not", since = "4.4", authorized = {RoleType.Admin}) private Boolean display; @@ -58,6 +66,10 @@ public Long getVpcId() { return vpcId; } + public Long getIpAddressId() { + return ipAddressId; + } + @Deprecated public Boolean getDisplay() { return display; @@ -94,7 +106,7 @@ public String getEventType() { @Override public void execute() { - CallContext.current().setEventDetails("VPN gateway Id: " + getEntityId()); + CallContext.current().setEventDetails("VPN gateway ID: " + getEntityUuid()); Site2SiteVpnGateway result = _s2sVpnService.getVpnGateway(getEntityId()); if (result != null) { Site2SiteVpnGatewayResponse response = _responseGenerator.createSite2SiteVpnGatewayResponse(result); diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java index 91e1cd1e56c7..b1fc331f4c3a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteRemoteAccessVpnCmd.java @@ -76,7 +76,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Delete Remote Access VPN for Account " + getEntityOwnerId() + " for ip id=" + publicIpId; + return "Delete Remote Access VPN for Account " + getEntityOwnerId() + " for IP: " + getResourceUuid(ApiConstants.PUBLIC_IP_ID); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnConnectionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnConnectionCmd.java index f66fe237a996..b23e6c163020 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnConnectionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnConnectionCmd.java @@ -65,7 +65,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Delete site-to-site VPN connection for Account " + getEntityOwnerId(); + return "Deleting site-to-site VPN connection for Account " + getEntityOwnerId(); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnCustomerGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnCustomerGatewayCmd.java index 0d43477205ec..9057620e0ddb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnCustomerGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/DeleteVpnCustomerGatewayCmd.java @@ -72,7 +72,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Delete site-to-site VPN customer gateway for Account " + getEntityOwnerId(); + return "Deleting site-to-site VPN customer gateway for Account " + getEntityOwnerId(); } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/RemoveVpnUserCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/RemoveVpnUserCmd.java index aff87105f9cb..a18619c89498 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/RemoveVpnUserCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/RemoveVpnUserCmd.java @@ -82,7 +82,7 @@ public Long getProjecId() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, projectId, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, projectId, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ResetVpnConnectionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ResetVpnConnectionCmd.java index b6e29e66ff4b..f681c8cce182 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ResetVpnConnectionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/ResetVpnConnectionCmd.java @@ -74,7 +74,7 @@ public Long getId() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, null, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, null, true); if (accountId == null) { return CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java index a4a0c927a576..92f6786268ad 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnConnectionCmd.java @@ -66,7 +66,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Updating site-to-site VPN connection id= " + id; + return "Updating site-to-site VPN connection ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnCustomerGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnCustomerGatewayCmd.java index edd168f08373..56aa8b2cd16d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnCustomerGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnCustomerGatewayCmd.java @@ -161,7 +161,7 @@ public String getIkeVersion() { @Override public long getEntityOwnerId() { - Long accountId = _accountService.finalyzeAccountId(accountName, domainId, null, true); + Long accountId = _accountService.finalizeAccountId(accountName, domainId, null, true); if (accountId == null) { accountId = CallContext.current().getCallingAccount().getId(); } diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java index 29676bdf0cc4..25076991217f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/vpn/UpdateVpnGatewayCmd.java @@ -63,7 +63,7 @@ public long getEntityOwnerId() { @Override public String getEventDescription() { - return "Update site-to-site VPN gateway id= " + id; + return "Update site-to-site VPN gateway ID = " + id; } @Override diff --git a/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java b/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java index 2504118877e2..55f70b9edcdf 100644 --- a/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java +++ b/api/src/main/java/org/apache/cloudstack/api/command/user/zone/ListZonesCmd.java @@ -67,6 +67,11 @@ public class ListZonesCmd extends BaseListCmd implements UserCmd { @Parameter(name = ApiConstants.SHOW_RESOURCE_ICON, type = CommandType.BOOLEAN, description = "Flag to display the resource image for the zones") private Boolean showIcon; + @Parameter(name = ApiConstants.STORAGE_ACCESS_GROUP, type = CommandType.STRING, + description = "the name of the storage access group", + since = "4.21.0") + private String storageAccessGroup; + ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// ///////////////////////////////////////////////////// @@ -107,6 +112,18 @@ public Boolean getShowIcon () { return showIcon != null ? showIcon : false; } + public String getStorageAccessGroup() { + return storageAccessGroup; + } + + public ListZonesCmd() { + + } + + public ListZonesCmd(String storageAccessGroup) { + this.storageAccessGroup = storageAccessGroup; + } + ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// diff --git a/api/src/main/java/org/apache/cloudstack/api/command/utils/OfferingUtils.java b/api/src/main/java/org/apache/cloudstack/api/command/utils/OfferingUtils.java new file mode 100644 index 000000000000..433a37c07cde --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/command/utils/OfferingUtils.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.utils; + +import com.cloud.offering.NetworkOffering; + +public class OfferingUtils { + + private OfferingUtils() { + } + + public static boolean isNetrisNatted(String provider, String networkMode) { + return "Netris".equalsIgnoreCase(provider) && + NetworkOffering.NetworkMode.NATTED.name().equalsIgnoreCase(networkMode); + } + + public static boolean isNsxWithoutLb(String provider, boolean nsxSupportsLbService) { + return "Nsx".equalsIgnoreCase(provider) && !nsxSupportsLbService; + } + + public static boolean isNetrisRouted(String provider, String networkMode) { + return "Netris".equalsIgnoreCase(provider) && NetworkOffering.NetworkMode.ROUTED.name().equalsIgnoreCase(networkMode); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java index 22bc6c76fc3b..93529b8a8887 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AccountResponse.java @@ -127,6 +127,30 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "The total number of Snapshots available for this Account") private String snapshotAvailable; + @SerializedName(ApiConstants.BACKUP_LIMIT) + @Param(description = "the total number of backups which can be stored by this account", since = "4.21.0") + private String backupLimit; + + @SerializedName(ApiConstants.BACKUP_TOTAL) + @Param(description = "the total number of backups stored by this account", since = "4.21.0") + private Long backupTotal; + + @SerializedName(ApiConstants.BACKUP_AVAILABLE) + @Param(description = "the total number of backups available to this account", since = "4.21.0") + private String backupAvailable; + + @SerializedName(ApiConstants.BACKUP_STORAGE_LIMIT) + @Param(description = "the total backup storage space (in GiB) the account can own", since = "4.21.0") + private String backupStorageLimit; + + @SerializedName(ApiConstants.BACKUP_STORAGE_TOTAL) + @Param(description = "the total backup storage space (in GiB) owned by the account", since = "4.21.0") + private Long backupStorageTotal; + + @SerializedName(ApiConstants.BACKUP_STORAGE_AVAILABLE) + @Param(description = "the total backup storage space (in GiB) available to the account", since = "4.21.0") + private String backupStorageAvailable; + @SerializedName("templatelimit") @Param(description = "The total number of Templates which can be created by this Account") private String templateLimit; @@ -207,6 +231,18 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "The total memory (in MB) available to be created for this account", since = "4.2.0") private String memoryAvailable; + @SerializedName("gpulimit") + @Param(description = "the total number of gpus the account can own", since = "4.21.0") + private String gpuLimit; + + @SerializedName("gputotal") + @Param(description = "the total number of gpus owned by account", since = "4.21.0") + private Long gpuTotal; + + @SerializedName("gpuavailable") + @Param(description = "the total number of gpus available to be created for this account", since = "4.21.0") + private String gpuAvailable; + @SerializedName("primarystoragelimit") @Param(description = "The total primary storage space (in GiB) the account can own", since = "4.2.0") private String primaryStorageLimit; @@ -231,6 +267,30 @@ public class AccountResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "The total secondary storage space (in GiB) available to be used for this account", since = "4.2.0") private String secondaryStorageAvailable; + @SerializedName(ApiConstants.BUCKET_LIMIT) + @Param(description = "the total number of buckets which can be stored by this account", since = "4.21.0") + private String bucketLimit; + + @SerializedName(ApiConstants.BUCKET_TOTAL) + @Param(description = "the total number of buckets stored by this account", since = "4.21.0") + private Long bucketTotal; + + @SerializedName(ApiConstants.BUCKET_AVAILABLE) + @Param(description = "the total number of buckets available to this account", since = "4.21.0") + private String bucketAvailable; + + @SerializedName(ApiConstants.OBJECT_STORAGE_LIMIT) + @Param(description = "the total object storage space (in GiB) the account can own", since = "4.21.0") + private String objectStorageLimit; + + @SerializedName(ApiConstants.OBJECT_STORAGE_TOTAL) + @Param(description = "the total object storage space (in GiB) owned by the account", since = "4.21.0") + private Long objectStorageTotal; + + @SerializedName(ApiConstants.OBJECT_STORAGE_AVAILABLE) + @Param(description = "the total object storage space (in GiB) available to the account", since = "4.21.0") + private String objectStorageAvailable; + @SerializedName(ApiConstants.STATE) @Param(description = "The state of the account") private String state; @@ -386,6 +446,36 @@ public void setSnapshotAvailable(String snapshotAvailable) { this.snapshotAvailable = snapshotAvailable; } + @Override + public void setBackupLimit(String backupLimit) { + this.backupLimit = backupLimit; + } + + @Override + public void setBackupTotal(Long backupTotal) { + this.backupTotal = backupTotal; + } + + @Override + public void setBackupAvailable(String backupAvailable) { + this.backupAvailable = backupAvailable; + } + + @Override + public void setBackupStorageLimit(String backupStorageLimit) { + this.backupStorageLimit = backupStorageLimit; + } + + @Override + public void setBackupStorageTotal(Long backupStorageTotal) { + this.backupStorageTotal = backupStorageTotal; + } + + @Override + public void setBackupStorageAvailable(String backupStorageAvailable) { + this.backupStorageAvailable = backupStorageAvailable; + } + @Override public void setTemplateLimit(String templateLimit) { this.templateLimit = templateLimit; @@ -411,6 +501,21 @@ public void setVmRunning(Integer vmRunning) { this.vmRunning = vmRunning; } + @Override + public void setGpuLimit(String gpuLimit) { + this.gpuLimit = gpuLimit; + } + + @Override + public void setGpuTotal(Long gpuTotal) { + this.gpuTotal = gpuTotal; + } + + @Override + public void setGpuAvailable(String gpuAvailable) { + this.gpuAvailable = gpuAvailable; + } + public void setState(String state) { this.state = state; } @@ -537,6 +642,36 @@ public void setSecondaryStorageAvailable(String secondaryStorageAvailable) { this.secondaryStorageAvailable = secondaryStorageAvailable; } + @Override + public void setBucketLimit(String bucketLimit) { + this.bucketLimit = bucketLimit; + } + + @Override + public void setBucketTotal(Long bucketTotal) { + this.bucketTotal = bucketTotal; + } + + @Override + public void setBucketAvailable(String bucketAvailable) { + this.bucketAvailable = bucketAvailable; + } + + @Override + public void setObjectStorageLimit(String objectStorageLimit) { + this.objectStorageLimit = objectStorageLimit; + } + + @Override + public void setObjectStorageTotal(Long objectStorageTotal) { + this.objectStorageTotal = objectStorageTotal; + } + + @Override + public void setObjectStorageAvailable(String objectStorageAvailable) { + this.objectStorageAvailable = objectStorageAvailable; + } + public void setDefaultZone(String defaultZoneId) { this.defaultZoneId = defaultZoneId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AlertTypeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AlertTypeResponse.java index 3f91cde01788..e8c3cf6c4ac7 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AlertTypeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AlertTypeResponse.java @@ -16,11 +16,12 @@ // under the License. package org.apache.cloudstack.api.response; -import com.cloud.serializer.Param; -import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + public class AlertTypeResponse extends BaseResponse { @SerializedName("alerttypeid") @@ -31,6 +32,10 @@ public class AlertTypeResponse extends BaseResponse { @Param(description = "description of alert type") private String name; + @SerializedName(ApiConstants.REPETITION_ALLOWED) + @Param(description = "Whether repetitive alerts allowed for the alert type", since = "4.22.0") + private boolean repetitionAllowed = true; + public String getName() { return name; } @@ -47,9 +52,10 @@ public void setUsageType(short alertType) { this.alertType = alertType; } - public AlertTypeResponse(short alertType, String name) { + public AlertTypeResponse(short alertType, String name, boolean repetitionAllowed) { this.alertType = alertType; this.name = name; + this.repetitionAllowed = repetitionAllowed; setObjectName("alerttype"); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ApiKeyPairResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ApiKeyPairResponse.java new file mode 100644 index 000000000000..350d71b37887 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ApiKeyPairResponse.java @@ -0,0 +1,285 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.user.ApiKeyPairState; +import com.google.gson.annotations.SerializedName; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; +import org.apache.cloudstack.api.ApiConstants; + +import com.cloud.serializer.Param; +import org.apache.cloudstack.api.BaseResponseWithAnnotations; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = ApiKeyPair.class) +public class ApiKeyPairResponse extends BaseResponseWithAnnotations { + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the API key pair") + private String name; + + @SerializedName(ApiConstants.API_KEY) + @Param(description = "The API key of the registered user.", isSensitive = true) + private String userApiKey; + + @SerializedName(ApiConstants.SECRET_KEY) + @Param(description = "The secret key of the registered user.", isSensitive = true) + private String userSecretKey; + + @SerializedName(ApiConstants.USER_ID) + @Param(description = "ID of the user that owns the keypair.") + private String userId; + + @SerializedName(ApiConstants.USERNAME) + @Param(description = "Username of the keypair's owner.") + private String username; + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the API key pair.", isSensitive = true) + private String id; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "API key pair description.") + private String description; + + @SerializedName(ApiConstants.START_DATE) + @Param(description = "API key pair start date.") + private Date startDate; + + @SerializedName(ApiConstants.END_DATE) + @Param(description = "API key pair expiration date.") + private Date endDate; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "API key pair creation timestamp.") + private Date created; + + @SerializedName(ApiConstants.ACCOUNT_TYPE) + @Param(description = "Account type.") + private String accountType; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "Account ID.") + private String accountId; + + @SerializedName(ApiConstants.ACCOUNT_NAME) + @Param(description = "Account name.") + private String accountName; + + @SerializedName(ApiConstants.ROLE_ID) + @Param(description = "ID of the role.") + private String roleId; + + @SerializedName(ApiConstants.ROLE_TYPE) + @Param(description = "Type of the role (Admin, ResourceAdmin, DomainAdmin, User).") + private String roleType; + + @SerializedName(ApiConstants.ROLE_NAME) + @Param(description = "Name of the role.") + private String roleName; + + @SerializedName(ApiConstants.PERMISSIONS) + @Param(description = "Permissions of the API key pair.") + private List permissions; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "ID of the domain which the account belongs to.") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "Name of the domain which the account belongs to.") + private String domainName; + + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "Path of the domain which the account belongs to.") + private String domainPath; + + @SerializedName(ApiConstants.STATE) + @Param(description = "State of the API key pair.") + private ApiKeyPairState state; + + public String getApiKey() { + return userApiKey; + } + + public void setApiKey(String apiKey) { + this.userApiKey = apiKey; + } + + public String getSecretKey() { + return userSecretKey; + } + + public void setSecretKey(String secretKey) { + this.userSecretKey = secretKey; + } + + public String getId() { + return this.id; + } + + public void setId(String id) { + this.id = id; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Date getStartDate() { + return startDate; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getAccountType() { + return accountType; + } + + public void setAccountType(String accountType) { + this.accountType = accountType; + } + + public String getRoleId() { + return roleId; + } + + public void setRoleId(String roleId) { + this.roleId = roleId; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getRoleType() { + return roleType; + } + + public void setRoleType(String roleType) { + this.roleType = roleType; + } + + public String getRoleName() { + return roleName; + } + + public void setRoleName(String roleName) { + this.roleName = roleName; + } + + public String getDomainId() { + return domainId; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getDomainName() { + return domainName; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public String getDomainPath() { + return domainPath; + } + + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + public ApiKeyPairState getState() { + return state; + } + + public void setState(ApiKeyPairState state) { + this.state = state; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public List getPermissions() { + return permissions; + } + + public void setPermissions(List permissions) { + this.permissions = permissions; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java index c769177b68f5..5be0052069ec 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/AsyncJobResponse.java @@ -55,10 +55,6 @@ public class AsyncJobResponse extends BaseResponse { @Param(description = "The async command executed") private String cmd; - @SerializedName("jobstatus") - @Param(description = "The current job status-should be 0 for PENDING") - private Integer jobStatus; - @SerializedName("jobprocstatus") @Param(description = "The progress information of the PENDING job") private Integer jobProcStatus; @@ -83,9 +79,13 @@ public class AsyncJobResponse extends BaseResponse { @Param(description = "The unique ID of the Instance/entity object related to the job") private String jobInstanceId; - @SerializedName("managementserverid") - @Param(description = "The msid of the management server on which the job is running", since = "4.19") - private Long msid; + @SerializedName(ApiConstants.MANAGEMENT_SERVER_ID) + @Param(description = "The MSID of the management server on which the job is running", since = "4.19") + private String managementServerId; + + @SerializedName(ApiConstants.MANAGEMENT_SERVER_NAME) + @Param(description = "the management server name of the host", since = "4.21.0") + private String managementServerName; @SerializedName(ApiConstants.CREATED) @Param(description = "The created date of the job") @@ -119,11 +119,6 @@ public void setCmd(String cmd) { this.cmd = cmd; } - @Override - public void setJobStatus(Integer jobStatus) { - this.jobStatus = jobStatus; - } - public void setJobProcStatus(Integer jobProcStatus) { this.jobProcStatus = jobProcStatus; } @@ -156,7 +151,11 @@ public void setRemoved(final Date removed) { this.removed = removed; } - public void setMsid(Long msid) { - this.msid = msid; + public void setManagementServerId(String managementServerId) { + this.managementServerId = managementServerId; + } + + public void setManagementServerName(String managementServerName) { + this.managementServerName = managementServerName; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/AttachedIsoResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/AttachedIsoResponse.java new file mode 100644 index 000000000000..b259de56218b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/AttachedIsoResponse.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class AttachedIsoResponse extends BaseResponse { + + @SerializedName("id") + @Param(description = "The ID of the attached ISO") + private String id; + + @SerializedName("name") + @Param(description = "The name of the attached ISO") + private String name; + + @SerializedName("displaytext") + @Param(description = "The display text of the attached ISO") + private String displayText; + + @SerializedName("deviceseq") + @Param(description = "The cdrom slot that holds this ISO (3=hdc, 4=hdd, ...)") + private Integer deviceSeq; + + @SerializedName("bootable") + @Param(description = "Whether this is the bootable ISO for the VM") + private Boolean bootable; + + public AttachedIsoResponse() { + } + + public AttachedIsoResponse(String id, String name, String displayText, Integer deviceSeq, boolean bootable) { + this.id = id; + this.name = name; + this.displayText = displayText; + this.deviceSeq = deviceSeq; + this.bootable = bootable; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDisplayText() { + return displayText; + } + + public Integer getDeviceSeq() { + return deviceSeq; + } + + public Boolean getBootable() { + return bootable; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java index 3d4f7f706a0f..69bee63e652f 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupOfferingResponse.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.api.response; import java.util.Date; +import java.util.Map; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -41,6 +42,10 @@ public class BackupOfferingResponse extends BaseResponse { @Param(description = "Description for the backup offering") private String description; + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "provider name", since = "4.21.0") + private String provider; + @SerializedName(ApiConstants.EXTERNAL_ID) @Param(description = "External ID on the provider side") private String externalId; @@ -57,10 +62,28 @@ public class BackupOfferingResponse extends BaseResponse { @Param(description = "Zone name") private String zoneName; + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the domain ID(s) this backup offering belongs to.", + since = "4.23.0") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the domain name(s) this backup offering belongs to.", + since = "4.23.0") + private String domain; + + @SerializedName(ApiConstants.CROSS_ZONE_INSTANCE_CREATION) + @Param(description = "the backups with this offering can be used to create Instances on all Zones", since = "4.22.0") + private Boolean crossZoneInstanceCreation; + @SerializedName(ApiConstants.CREATED) @Param(description = "The date this backup offering was created") private Date created; + @SerializedName(ApiConstants.BACKUP_OFFERING_DETAILS) + @Param(description = "Details for the backup offering", since = "4.23.0") + private Map details; + public void setId(String id) { this.id = id; } @@ -69,6 +92,10 @@ public void setExternalId(String externalId) { this.externalId = externalId; } + public void setProvider(String provider) { + this.provider = provider; + } + public void setName(String name) { this.name = name; } @@ -89,7 +116,23 @@ public void setZoneName(String zoneName) { this.zoneName = zoneName; } + public void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation) { + this.crossZoneInstanceCreation = crossZoneInstanceCreation; + } + public void setCreated(Date created) { this.created = created; } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public void setDetails(Map details) { + this.details = details; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java index 0db51f040349..0d3c830950b2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupRepositoryResponse.java @@ -57,10 +57,18 @@ public class BackupRepositoryResponse extends BaseResponse { @Param(description = "backup type") private String type; + @SerializedName(ApiConstants.MOUNT_OPTIONS) + @Param(description = "mount options", since = "4.22.1") + private String mountOptions; + @SerializedName(ApiConstants.CAPACITY_BYTES) @Param(description = "capacity of the backup repository") private Long capacityBytes; + @SerializedName(ApiConstants.CROSS_ZONE_INSTANCE_CREATION) + @Param(description = "the backups in this repository can be used to create Instances on all Zones") + private Boolean crossZoneInstanceCreation; + @SerializedName("created") @Param(description = "the date and time the backup repository was added") private Date created; @@ -124,6 +132,14 @@ public void setType(String type) { this.type = type; } + public String getMountOptions() { + return mountOptions; + } + + public void setMountOptions(String mountOptions) { + this.mountOptions = mountOptions; + } + public Long getCapacityBytes() { return capacityBytes; } @@ -132,6 +148,14 @@ public void setCapacityBytes(Long capacityBytes) { this.capacityBytes = capacityBytes; } + public Boolean getCrossZoneInstanceCreation() { + return crossZoneInstanceCreation; + } + + public void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation) { + this.crossZoneInstanceCreation = crossZoneInstanceCreation; + } + public Date getCreated() { return created; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java index 44b67ed996b9..70db01445edd 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupResponse.java @@ -26,6 +26,7 @@ import com.google.gson.annotations.SerializedName; import java.util.Date; +import java.util.Map; @EntityReference(value = Backup.class) public class BackupResponse extends BaseResponse { @@ -34,6 +35,14 @@ public class BackupResponse extends BaseResponse { @Param(description = "ID of the Instance backup") private String id; + @SerializedName(ApiConstants.NAME) + @Param(description = "name of the backup", since = "4.21.0") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "description for the backup", since = "4.21.0") + private String description; + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) @Param(description = "ID of the Instance") private String vmId; @@ -62,10 +71,22 @@ public class BackupResponse extends BaseResponse { @Param(description = "Backup protected (virtual) size in bytes") private Long protectedSize; + @SerializedName(ApiConstants.UNCOMPRESSED_SIZE) + @Param(description = "Backup uncompressed size in bytes. Only defined if backup is compressed.") + private Long uncompressedSize; + @SerializedName(ApiConstants.STATUS) @Param(description = "Backup status") private Backup.Status status; + @SerializedName(ApiConstants.COMPRESSION_STATUS) + @Param(description = "Backup compression status.") + private Backup.CompressionStatus compressionStatus; + + @SerializedName(ApiConstants.VALIDATION_STATUS) + @Param(description = "Backup validation status.") + private Backup.ValidationStatus validationStatus; + @SerializedName(ApiConstants.VOLUMES) @Param(description = "Backed up volumes") private String volumes; @@ -102,6 +123,34 @@ public class BackupResponse extends BaseResponse { @Param(description = "Zone name") private String zone; + @SerializedName(ApiConstants.VM_DETAILS) + @Param(description = "Lists the vm specific details for the backup", since = "4.21.0") + private Map vmDetails; + + @SerializedName(ApiConstants.INTERVAL_TYPE) + @Param(description = "Interval type of the backup", since = "4.21.0") + private String intervalType; + + @SerializedName(ApiConstants.BACKUP_VM_OFFERING_REMOVED) + @Param(description = "The backup offering corresponding to this backup was removed from the VM", since = "4.21.0") + private Boolean vmOfferingRemoved; + + @SerializedName(ApiConstants.IS_BACKUP_VM_EXPUNGED) + @Param(description = "Indicates whether the VM from which the backup was taken is expunged or not", since = "4.22.0") + private Boolean isVmExpunged; + + @SerializedName(ApiConstants.FROM_CHECKPOINT_ID) + @Param(description = "Previous active checkpoint ID for incremental backups", since = "4.23.0") + private String fromCheckpointId; + + @SerializedName(ApiConstants.TO_CHECKPOINT_ID) + @Param(description = "Next checkpoint ID for incremental backups", since = "4.23.0") + private String toCheckpointId; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "Host ID where the backup is running", since = "4.23.0") + private String hostId; + public String getId() { return id; } @@ -110,6 +159,22 @@ public void setId(String id) { this.id = id; } + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public String getVmId() { return vmId; } @@ -166,6 +231,14 @@ public void setProtectedSize(Long protectedSize) { this.protectedSize = protectedSize; } + public Long getUncompressedSize() { + return uncompressedSize; + } + + public void setUncompressedSize(Long uncompressedSize) { + this.uncompressedSize = uncompressedSize; + } + public Backup.Status getStatus() { return status; } @@ -174,6 +247,22 @@ public void setStatus(Backup.Status status) { this.status = status; } + public Backup.CompressionStatus getCompressionStatus() { + return compressionStatus; + } + + public void setCompressionStatus(Backup.CompressionStatus compressionStatus) { + this.compressionStatus = compressionStatus; + } + + public Backup.ValidationStatus getValidationStatus() { + return validationStatus; + } + + public void setValidationStatus(Backup.ValidationStatus validationStatus) { + this.validationStatus = validationStatus; + } + public String getVolumes() { return volumes; } @@ -245,4 +334,56 @@ public String getZone() { public void setZone(String zone) { this.zone = zone; } + + public Map getVmDetails() { + return vmDetails; + } + + public void setVmDetails(Map vmDetails) { + this.vmDetails = vmDetails; + } + + public String getIntervalType() { + return this.intervalType; + } + + public void setIntervalType(String intervalType) { + this.intervalType = intervalType; + } + + public Boolean getVmOfferingRemoved() { + return this.vmOfferingRemoved; + } + + public void setVmOfferingRemoved(Boolean vmOfferingRemoved) { + this.vmOfferingRemoved = vmOfferingRemoved; + } + + public void setVmExpunged(Boolean isVmExpunged) { + this.isVmExpunged = isVmExpunged; + } + + public void setFromCheckpointId(String fromCheckpointId) { + this.fromCheckpointId = fromCheckpointId; + } + + public String getFromCheckpointId() { + return this.fromCheckpointId; + } + + public void setToCheckpointId(String toCheckpointId) { + this.toCheckpointId = toCheckpointId; + } + + public String getToCheckpointId() { + return this.toCheckpointId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public String getHostId() { + return this.hostId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java index b9987a8e25bc..5da07864603a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupScheduleResponse.java @@ -28,6 +28,9 @@ @EntityReference(value = BackupSchedule.class) public class BackupScheduleResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the backup schedule.", since = "4.21.0") + private String id; @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) @Param(description = "Name of the Instance") @@ -37,18 +40,34 @@ public class BackupScheduleResponse extends BaseResponse { @Param(description = "ID of the Instance") private String vmId; - @SerializedName("schedule") - @Param(description = "Time the backup is scheduled to be taken.") + @SerializedName(ApiConstants.SCHEDULE) + @Param(description = "The time the backup is scheduled to be taken.") private String schedule; - @SerializedName("intervaltype") + @SerializedName(ApiConstants.INTERVAL_TYPE) @Param(description = "The interval type of the backup schedule") private DateUtil.IntervalType intervalType; - @SerializedName("timezone") + @SerializedName(ApiConstants.TIMEZONE) @Param(description = "The time zone of the backup schedule") private String timezone; + @SerializedName(ApiConstants.MAX_BACKUPS) + @Param(description = "maximum number of backups retained") + private Integer maxBackups; + + @SerializedName(ApiConstants.ISOLATED) + @Param(description = ApiConstants.PARAMETER_DESCRIPTION_ISOLATED_BACKUPS) + private boolean isolated; + + public void setId(String id) { + this.id = id; + } + + @SerializedName(ApiConstants.QUIESCE_VM) + @Param(description = "quiesce the instance before checkpointing the disks for backup") + private Boolean quiesceVM; + public String getVmName() { return vmName; } @@ -88,4 +107,16 @@ public String getTimezone() { public void setTimezone(String timezone) { this.timezone = timezone; } + + public void setMaxBackups(Integer maxBackups) { + this.maxBackups = maxBackups; + } + + public void setQuiesceVM(Boolean quiesceVM) { + this.quiesceVM = quiesceVM; + } + + public void setIsolated(boolean isolated) { + this.isolated = isolated; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BackupServiceJobResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BackupServiceJobResponse.java new file mode 100644 index 000000000000..0d4984fe34f5 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/BackupServiceJobResponse.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import java.util.Date; + +public class BackupServiceJobResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "Compression job ID.") + private Long id; + + @SerializedName(ApiConstants.BACKUP_ID) + @Param(description = "Backup ID.") + private String backupId; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "Host where the job is being executed.") + private String hostId; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "Zone where the job is being executed.") + private String zoneId; + + @SerializedName(ApiConstants.ATTEMPTS) + @Param(description = "Number of attempts already made to complete this job.") + private Integer attempts; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "Compression job type.") + private String type; + + @SerializedName(ApiConstants.START_DATE) + @Param(description = "Compression job start date.") + private Date startDate; + + @SerializedName(ApiConstants.SCHEDULED_DATE) + @Param(description = "Compression job scheduled start date.") + private Date scheduledDate; + + @SerializedName(ApiConstants.REMOVED) + @Param(description = "Compression job scheduled removed date.") + private Date removed; + + public BackupServiceJobResponse(Long id, String backupId, String zoneId, Integer attempts, String type, Date startDate, Date scheduledDate, Date removed) { + super("backupservicejob"); + this.id = id; + this.backupId = backupId; + this.zoneId = zoneId; + this.attempts = attempts; + this.type = type; + this.startDate = startDate; + this.scheduledDate = scheduledDate; + this.removed = removed; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java index f2dd365452cd..cde140839ec0 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/BucketResponse.java @@ -75,7 +75,7 @@ public class BucketResponse extends BaseResponseWithTagInformation implements Co private String state; @SerializedName(ApiConstants.QUOTA) - @Param(description = "Bucket Quota in GB") + @Param(description = "Bucket Quota in GiB") private Integer quota; @SerializedName(ApiConstants.ENCRYPTION) diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java index 6b8a2828c70d..7ef627ec33ce 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CapabilitiesResponse.java @@ -16,6 +16,9 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.Map; + +import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -72,6 +75,10 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "True if Snapshot is supported for KVM host, false otherwise") private boolean kvmSnapshotEnabled; + @SerializedName("snapshotshowchainsize") + @Param(description = "True to show the parent and chain size (sum of physical size of snapshot and all its parents) for incremental snapshots", since = "4.22.1") + private boolean snapshotShowChainSize; + @SerializedName("apilimitmax") @Param(description = "Max allowed number of api requests within the specified interval") private Integer apiLimitMax; @@ -136,6 +143,14 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "the min Ram size for the service offering used by the shared filesystem instance", since = "4.20.0") private Integer sharedFsVmMinRamSize; + @SerializedName(ApiConstants.INSTANCE_LEASE_ENABLED) + @Param(description = "true if instance lease feature is enabled", since = "4.21.0") + private Boolean instanceLeaseEnabled; + + @SerializedName(ApiConstants.EXTENSIONS_PATH) + @Param(description = "The path of the extensions directory", since = "4.21.0", authorized = {RoleType.Admin}) + private String extensionsPath; + @SerializedName(ApiConstants.DYNAMIC_SCALING_ENABLED) @Param(description = "true if dynamically scaling for instances is enabled", since = "4.21.0") private Boolean dynamicScalingEnabled; @@ -144,6 +159,10 @@ public class CapabilitiesResponse extends BaseResponse { @Param(description = "true if additional configurations or extraconfig can be passed to Instances", since = "4.20.2") private Boolean additionalConfigEnabled; + @SerializedName(ApiConstants.VPN_CUSTOMER_GATEWAY_PARAMETERS) + @Param(description = "Excluded and obsolete VPN customer gateway cryptographic parameters") + private Map vpnCustomerGatewayParameters; + public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } @@ -188,6 +207,10 @@ public void setKVMSnapshotEnabled(boolean kvmSnapshotEnabled) { this.kvmSnapshotEnabled = kvmSnapshotEnabled; } + public void setSnapshotShowChainSize(boolean snapshotShowChainSize) { + this.snapshotShowChainSize = snapshotShowChainSize; + } + public void setApiLimitInterval(Integer apiLimitInterval) { this.apiLimitInterval = apiLimitInterval; } @@ -256,6 +279,14 @@ public void setSharedFsVmMinRamSize(Integer sharedFsVmMinRamSize) { this.sharedFsVmMinRamSize = sharedFsVmMinRamSize; } + public void setInstanceLeaseEnabled(Boolean instanceLeaseEnabled) { + this.instanceLeaseEnabled = instanceLeaseEnabled; + } + + public void setExtensionsPath(String extensionsPath) { + this.extensionsPath = extensionsPath; + } + public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { this.dynamicScalingEnabled = dynamicScalingEnabled; } @@ -263,4 +294,8 @@ public void setDynamicScalingEnabled(Boolean dynamicScalingEnabled) { public void setAdditionalConfigEnabled(Boolean additionalConfigEnabled) { this.additionalConfigEnabled = additionalConfigEnabled; } + + public void setVpnCustomerGatewayParameters(Map vpnCustomerGatewayParameters) { + this.vpnCustomerGatewayParameters = vpnCustomerGatewayParameters; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CheckpointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CheckpointResponse.java new file mode 100644 index 000000000000..2bec7711064f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/CheckpointResponse.java @@ -0,0 +1,53 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class CheckpointResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the checkpoint ID") + private String id; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the checkpoint creation time") + private Date created; + + @SerializedName(ApiConstants.IS_ACTIVE) + @Param(description = "whether this is the active checkpoint") + private Boolean isActive; + + public void setId(String id) { + this.id = id; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setIsActive(Boolean isActive) { + this.isActive = isActive; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java index 4114c228e265..f399c1260f77 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterDrsPlanMigrationResponse.java @@ -50,13 +50,13 @@ public class ClusterDrsPlanMigrationResponse extends BaseResponse { @Param(description = "Destination host for VM migration") String destHostName; - @SerializedName(ApiConstants.JOB_ID) + @SerializedName(ApiConstants.MIGRATION_JOB_ID) @Param(description = "id of VM migration async job") - private Long jobId; + private Long migrationJobId; - @SerializedName(ApiConstants.JOB_STATUS) + @SerializedName(ApiConstants.MIGRATION_JOB_STATUS) @Param(description = "Job status of VM migration async job") - private JobInfo.Status jobStatus; + private JobInfo.Status migrationJobStatus; public ClusterDrsPlanMigrationResponse(String vmId, String vmName, String srcHostId, String srcHostName, @@ -68,8 +68,8 @@ public ClusterDrsPlanMigrationResponse(String vmId, String vmName, String srcHos this.srcHostName = srcHostName; this.destHostId = destHostId; this.destHostName = destHostName; - this.jobId = jobId; - this.jobStatus = jobStatus; + this.migrationJobId = jobId; + this.migrationJobStatus = jobStatus; this.setObjectName(ApiConstants.MIGRATIONS); } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java index a2e8fba8e8c8..e73cd3876a92 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ClusterResponse.java @@ -31,6 +31,8 @@ @EntityReference(value = Cluster.class) public class ClusterResponse extends BaseResponseWithAnnotations { + private transient long internalId; + @SerializedName(ApiConstants.ID) @Param(description = "The cluster ID") private String id; @@ -95,6 +97,34 @@ public class ClusterResponse extends BaseResponseWithAnnotations { @Param(description = "CPU Arch of the hosts in the cluster", since = "4.20") private String arch; + @SerializedName(ApiConstants.STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups for the host", since = "4.21.0") + private String storageAccessGroups; + + @SerializedName(ApiConstants.POD_STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups on the pod", since = "4.21.0") + private String podStorageAccessGroups; + + @SerializedName(ApiConstants.ZONE_STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups on the zone", since = "4.21.0") + private String zoneStorageAccessGroups; + + @SerializedName(ApiConstants.EXTENSION_ID) + @Param(description="The ID of extension for this cluster", since = "4.21.0") + private String extensionId; + + @SerializedName(ApiConstants.EXTENSION_NAME) + @Param(description="The name of extension for this cluster", since = "4.21.0") + private String extensionName; + + public void setInternalId(long internalId) { + this.internalId = internalId; + } + + public long getInternalId() { + return internalId; + } + public String getId() { return id; } @@ -259,4 +289,44 @@ public void setArch(String arch) { public String getArch() { return arch; } + + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + + public String getPodStorageAccessGroups() { + return podStorageAccessGroups; + } + + public void setPodStorageAccessGroups(String podStorageAccessGroups) { + this.podStorageAccessGroups = podStorageAccessGroups; + } + + public String getZoneStorageAccessGroups() { + return zoneStorageAccessGroups; + } + + public void setZoneStorageAccessGroups(String zoneStorageAccessGroups) { + this.zoneStorageAccessGroups = zoneStorageAccessGroups; + } + + public void setExtensionId(String extensionId) { + this.extensionId = extensionId; + } + + public String getExtensionId() { + return extensionId; + } + + public void setExtensionName(String extensionName) { + this.extensionName = extensionName; + } + + public String getExtensionName() { + return extensionName; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java new file mode 100644 index 000000000000..85747d7c2a8f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ConsoleSessionResponse.java @@ -0,0 +1,236 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.import org.apache.cloudstack.context.CallContext; +package org.apache.cloudstack.api.response; + +import com.google.gson.annotations.SerializedName; + +import com.cloud.serializer.Param; +import org.apache.cloudstack.consoleproxy.ConsoleSession; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import java.util.Date; + +@EntityReference(value = ConsoleSession.class) +public class ConsoleSessionResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the console session.") + private String id; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Date when the console session's endpoint was created.") + private Date created; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "Domain of the account that created the console endpoint.") + private String domain; + + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "Domain path of the account that created the console endpoint.") + private String domainPath; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "Domain ID of the account that created the console endpoint.") + private String domainId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "Account that created the console endpoint.") + private String account; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "ID of the account that created the console endpoint.") + private String accountId; + + @SerializedName(ApiConstants.USER) + @Param(description = "User that created the console endpoint.") + private String user; + + @SerializedName(ApiConstants.USER_ID) + @Param(description = "ID of the user that created the console endpoint.") + private String userId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "ID of the virtual machine.") + private String vmId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "Name of the virtual machine.") + private String vmName; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "ID of the host.") + private String hostId; + + @SerializedName(ApiConstants.HOST_NAME) + @Param(description = "Name of the host.") + private String hostName; + + @SerializedName(ApiConstants.ACQUIRED) + @Param(description = "Date when the console session was acquired.") + private Date acquired; + + @SerializedName(ApiConstants.REMOVED) + @Param(description = "Date when the console session was removed.") + private Date removed; + + @SerializedName(ApiConstants.CONSOLE_ENDPOINT_CREATOR_ADDRESS) + @Param(description = "IP address of the creator of the console endpoint.") + private String consoleEndpointCreatorAddress; + + @SerializedName(ApiConstants.CLIENT_ADDRESS) + @Param(description = "IP address of the client that created the console session.") + private String clientAddress; + + public void setId(String id) { + this.id = id; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setDomain(String domain) { + this.domain = domain; + } + + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public void setAccount(String account) { + this.account = account; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public void setUser(String user) { + this.user = user; + } + + public void setUserId(String userId) { + this.userId = userId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public void setAcquired(Date acquired) { + this.acquired = acquired; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public void setConsoleEndpointCreatorAddress(String consoleEndpointCreatorAddress) { + this.consoleEndpointCreatorAddress = consoleEndpointCreatorAddress; + } + + public void setClientAddress(String clientAddress) { + this.clientAddress = clientAddress; + } + + public String getId() { + return id; + } + + public Date getCreated() { + return created; + } + + public String getDomain() { + return domain; + } + + public String getDomainPath() { + return domainPath; + } + + public String getDomainId() { + return domainId; + } + + public String getAccount() { + return account; + } + + public String getAccountId() { + return accountId; + } + + public String getUser() { + return user; + } + + public String getUserId() { + return userId; + } + + public String getVmId() { + return vmId; + } + + public String getVmName() { + return vmName; + } + + public String getHostId() { + return hostId; + } + + public String getHostName() { + return hostName; + } + + public Date getAcquired() { + return acquired; + } + + public Date getRemoved() { + return removed; + } + + public String getConsoleEndpointCreatorAddress() { + return consoleEndpointCreatorAddress; + } + + public String getClientAddress() { + return clientAddress; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/CreateConsoleEndpointResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/CreateConsoleEndpointResponse.java index 90c9743b66f0..557315442e4b 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/CreateConsoleEndpointResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/CreateConsoleEndpointResponse.java @@ -26,7 +26,7 @@ public class CreateConsoleEndpointResponse extends BaseResponse { public CreateConsoleEndpointResponse() { } - @SerializedName(ApiConstants.RESULT) + @SerializedName(ApiConstants.SUCCESS) @Param(description = "True if the console endpoint is generated properly") private Boolean result; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DnsProviderResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DnsProviderResponse.java new file mode 100644 index 000000000000..f3a571f40639 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DnsProviderResponse.java @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class DnsProviderResponse extends BaseResponse { + + @SerializedName(ApiConstants.NAME) + @Param(description = "The name of the DNS provider (e.g. PowerDNS, Cloudflare)") + private String name; + + public DnsProviderResponse(String name) { + this.name = name; + setObjectName("dnsprovider"); // Sets the JSON wrapper name + } + + // Accessors + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DnsRecordResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DnsRecordResponse.java new file mode 100644 index 000000000000..6e2082485512 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DnsRecordResponse.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.dns.DnsRecord; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class DnsRecordResponse extends BaseResponse { + + @SerializedName(ApiConstants.NAME) + @Param(description = "The record name (e.g., www.example.com.)") + private String name; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "The record type (e.g., A, CNAME, TXT)") + private DnsRecord.RecordType type; + + @SerializedName("contents") + @Param(description = "The contents of the record (IP address or target)") + private List contents; + + @SerializedName("ttl") + @Param(description = "Time to live (TTL) in seconds") + private Integer ttl; + + public DnsRecordResponse() { + super(); + setObjectName("dnsrecord"); + } + + // Setters + public void setName(String name) { this.name = name; } + public void setType(DnsRecord.RecordType type) { this.type = type; } + public void setContent(List contents) { this.contents = contents; } + public void setTtl(Integer ttl) { this.ttl = ttl; } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java new file mode 100644 index 000000000000..fecf49f0f984 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DnsServerResponse.java @@ -0,0 +1,133 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.dns.DnsServer; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = DnsServer.class) +public class DnsServerResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the DNS server") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the DNS server") + private String name; + + @SerializedName(ApiConstants.URL) + @Param(description = "URL of the DNS server API") + private String url; + + @SerializedName(ApiConstants.PORT) + @Param(description = "The port of the DNS server") + private Integer port; + + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "The provider type of the DNS server") + private String provider; + + @SerializedName(ApiConstants.IS_PUBLIC) + @Param(description = "Is the DNS server publicly available") + private Boolean isPublic; + + @SerializedName(ApiConstants.PUBLIC_DOMAIN_SUFFIX) + @Param(description = "The public domain suffix for the DNS server") + private String publicDomainSuffix; + + @SerializedName(ApiConstants.NAME_SERVERS) + @Param(description = "Name servers entries associated to DNS server") + private List nameServers; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account associated with the DNS server") + private String accountName; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the ID of the domain associated with the DNS server") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the name of the domain associated with the DNS server") + private String domainName; + + @SerializedName(ApiConstants.STATE) + @Param(description = "The state of the account") + private String state; + + public DnsServerResponse() { + super(); + + } + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setUrl(String url) { + this.url = url; + } + + public void setProvider(String provider) { + this.provider = provider; + } + + public void setPublic(Boolean value) { + isPublic = value; + } + + public void setPort(Integer port) { + this.port = port; + } + + public void setPublicDomainSuffix(String publicDomainSuffix) { + this.publicDomainSuffix = publicDomainSuffix; + } + + public void setNameServers(List nameServers) { + this.nameServers = nameServers; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DnsZoneNetworkMapResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DnsZoneNetworkMapResponse.java new file mode 100644 index 000000000000..84fe46ce5aca --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DnsZoneNetworkMapResponse.java @@ -0,0 +1,64 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class DnsZoneNetworkMapResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "The ID of the mapping") + private String id; + + @SerializedName(ApiConstants.DNS_ZONE_ID) + @Param(description = "The ID of the DNS zone") + private String dnsZoneId; + + @SerializedName(ApiConstants.NETWORK_ID) + @Param(description = "The ID of the Network") + private String networkId; + + @SerializedName("subdomain") + @Param(description = "The sub domain name of the auto-registered DNS record") + private String subDomain; + + public DnsZoneNetworkMapResponse() { + super(); + setObjectName("dnszonenetwork"); + } + + // Setters + public void setId(String id) { + this.id = id; + } + + public void setDnsZoneId(String dnsZoneId) { + this.dnsZoneId = dnsZoneId; + } + + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + public void setSubDomain(String subDomain) { + this.subDomain = subDomain; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DnsZoneResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DnsZoneResponse.java new file mode 100644 index 000000000000..e179d2fd4b13 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/DnsZoneResponse.java @@ -0,0 +1,139 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.dns.DnsZone; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = DnsZone.class) +public class DnsZoneResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the DNS zone") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the DNS zone") + private String name; + + @SerializedName("dnsserverid") + @Param(description = "ID of the DNS server this zone belongs to") + private String dnsServerId; + + @SerializedName("dnsservername") + @Param(description = "the name of the DNS server hosting this zone") + private String dnsServerName; + + @SerializedName("dnsserveraccount") + @Param(description = "the account name of the DNS server owner") + private String dnsServerAccountName; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account associated with the DNS zone") + private String accountName; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the name of the domain associated with the DNS zone") + private String domainName; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the ID of the domain associated with the DNS server") + private String domainId; + + @SerializedName(ApiConstants.NETWORK_ID) + @Param(description = "ID of the network this zone is associated with") + private String networkId; + + @SerializedName(ApiConstants.NETWORK_NAME) + @Param(description = "Name of the network this zone is associated with") + private String networkName; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "The type of the zone (Public/Private)") + private DnsZone.ZoneType type; + + @SerializedName(ApiConstants.STATE) + @Param(description = "The state of the zone (Active/Inactive)") + private DnsZone.State state; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description for the DNS zone") + private String description; + + public DnsZoneResponse() { + super(); + setObjectName("dnszone"); + } + + public void setName(String name) { + this.name = name; + } + + public void setDnsServerId(String dnsServerId) { + this.dnsServerId = dnsServerId; + } + + public void setNetworkId(String networkId) { + this.networkId = networkId; + } + + public void setNetworkName(String networkName) { + this.networkName = networkName; + } + + public void setType(DnsZone.ZoneType type) { + this.type = type; + } + + public void setState(DnsZone.State state) { + this.state = state; + } + + public void setId(String id) { + this.id = id; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setDnsServerName(String dnsServerName) { + this.dnsServerName = dnsServerName; + } + + public void setDnsServerAccountName(String dnsServerAccountName) { + this.dnsServerAccountName = dnsServerAccountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public void setDomainId(String domainId) { + this.domainId = domainId; + } + +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java index c848c67af467..453c6b229e97 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/DomainResponse.java @@ -16,21 +16,21 @@ // under the License. package org.apache.cloudstack.api.response; -import com.google.gson.annotations.SerializedName; +import java.util.Date; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.api.BaseResponseWithAnnotations; +import org.apache.cloudstack.api.BaseResponseWithTagInformation; import org.apache.cloudstack.api.EntityReference; import com.cloud.domain.Domain; import com.cloud.serializer.Param; - -import java.util.Date; -import java.util.List; -import java.util.Map; +import com.google.gson.annotations.SerializedName; @EntityReference(value = Domain.class) -public class DomainResponse extends BaseResponseWithAnnotations implements ResourceLimitAndCountResponse, SetResourceIconResponse { +public class DomainResponse extends BaseResponseWithTagInformation implements ResourceLimitAndCountResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "The ID of the domain") private String id; @@ -105,6 +105,30 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou @SerializedName("snapshotavailable") @Param(description = "The total number of Snapshots available for this domain") private String snapshotAvailable; + @SerializedName(ApiConstants.BACKUP_LIMIT) + @Param(description = "the total number of backups which can be stored by this domain", since = "4.21.0") + private String backupLimit; + + @SerializedName(ApiConstants.BACKUP_TOTAL) + @Param(description = "the total number of backups stored by this domain", since = "4.21.0") + private Long backupTotal; + + @SerializedName(ApiConstants.BACKUP_AVAILABLE) + @Param(description = "the total number of backups available to this domain", since = "4.21.0") + private String backupAvailable; + + @SerializedName(ApiConstants.BACKUP_STORAGE_LIMIT) + @Param(description = "the total backup storage space (in GiB) the domain can own", since = "4.21.0") + private String backupStorageLimit; + + @SerializedName(ApiConstants.BACKUP_STORAGE_TOTAL) + @Param(description = "the total backup storage space (in GiB) owned by the domain", since = "4.21.0") + private Long backupStorageTotal; + + @SerializedName(ApiConstants.BACKUP_STORAGE_AVAILABLE) + @Param(description = "the total backup storage space (in GiB) available to the domain", since = "4.21.0") + private String backupStorageAvailable; + @SerializedName("templatelimit") @Param(description = "The total number of Templates which can be created by this domain") private String templateLimit; @@ -159,6 +183,15 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou @SerializedName("memoryavailable") @Param(description = "The total memory (in MB) available to be created for this domain", since="4.2.0") private String memoryAvailable; + @SerializedName("gpulimit") @Param(description="the total number of gpus the domain can own", since="4.21.0") + private String gpuLimit; + + @SerializedName("gputotal") @Param(description="the total number of gpus owned by domain", since="4.21.0") + private Long gpuTotal; + + @SerializedName("gpuavailable") @Param(description="the total number of gpus available to be created for this domain", since="4.21.0") + private String gpuAvailable; + @SerializedName("primarystoragelimit") @Param(description = "The total primary storage space (in GiB) the domain can own", since="4.2.0") private String primaryStorageLimit; @@ -177,6 +210,30 @@ public class DomainResponse extends BaseResponseWithAnnotations implements Resou @SerializedName("secondarystorageavailable") @Param(description = "The total secondary storage space (in GiB) available to be used for this domain", since="4.2.0") private String secondaryStorageAvailable; + @SerializedName(ApiConstants.BUCKET_LIMIT) + @Param(description = "the total number of buckets which can be stored by this domain", since = "4.21.0") + private String bucketLimit; + + @SerializedName(ApiConstants.BUCKET_TOTAL) + @Param(description = "the total number of buckets stored by this domain", since = "4.21.0") + private Long bucketTotal; + + @SerializedName(ApiConstants.BUCKET_AVAILABLE) + @Param(description = "the total number of buckets available to this domain", since = "4.21.0") + private String bucketAvailable; + + @SerializedName(ApiConstants.OBJECT_STORAGE_LIMIT) + @Param(description = "the total object storage space (in GiB) the domain can own", since = "4.21.0") + private String objectStorageLimit; + + @SerializedName(ApiConstants.OBJECT_STORAGE_TOTAL) + @Param(description = "the total object storage space (in GiB) owned by the domain", since = "4.21.0") + private Long objectStorageTotal; + + @SerializedName(ApiConstants.OBJECT_STORAGE_AVAILABLE) + @Param(description = "the total object storage space (in GiB) available to the domain", since = "4.21.0") + private String objectStorageAvailable; + @SerializedName(ApiConstants.RESOURCE_ICON) @Param(description = "Base64 string representation of the resource icon", since = "4.16.0.0") ResourceIconResponse icon; @@ -313,6 +370,36 @@ public void setSnapshotAvailable(String snapshotAvailable) { this.snapshotAvailable = snapshotAvailable; } + @Override + public void setBackupLimit(String backupLimit) { + this.backupLimit = backupLimit; + } + + @Override + public void setBackupTotal(Long backupTotal) { + this.backupTotal = backupTotal; + } + + @Override + public void setBackupAvailable(String backupAvailable) { + this.backupAvailable = backupAvailable; + } + + @Override + public void setBackupStorageLimit(String backupStorageLimit) { + this.backupStorageLimit = backupStorageLimit; + } + + @Override + public void setBackupStorageTotal(Long backupStorageTotal) { + this.backupStorageTotal = backupStorageTotal; + } + + @Override + public void setBackupStorageAvailable(String backupStorageAvailable) { + this.backupStorageAvailable = backupStorageAvailable; + } + @Override public void setTemplateLimit(String templateLimit) { this.templateLimit = templateLimit; @@ -400,6 +487,21 @@ public void setMemoryAvailable(String memoryAvailable) { this.memoryAvailable = memoryAvailable; } + @Override + public void setGpuLimit(String gpuLimit) { + this.gpuLimit = gpuLimit; + } + + @Override + public void setGpuTotal(Long gpuTotal) { + this.gpuTotal = gpuTotal; + } + + @Override + public void setGpuAvailable(String gpuAvailable) { + this.gpuAvailable = gpuAvailable; + } + @Override public void setPrimaryStorageLimit(String primaryStorageLimit) { this.primaryStorageLimit = primaryStorageLimit; @@ -430,6 +532,36 @@ public void setSecondaryStorageAvailable(String secondaryStorageAvailable) { this.secondaryStorageAvailable = secondaryStorageAvailable; } + @Override + public void setBucketLimit(String bucketLimit) { + this.bucketLimit = bucketLimit; + } + + @Override + public void setBucketTotal(Long bucketTotal) { + this.bucketTotal = bucketTotal; + } + + @Override + public void setBucketAvailable(String bucketAvailable) { + this.bucketAvailable = bucketAvailable; + } + + @Override + public void setObjectStorageLimit(String objectStorageLimit) { + this.objectStorageLimit = objectStorageLimit; + } + + @Override + public void setObjectStorageTotal(Long objectStorageTotal) { + this.objectStorageTotal = objectStorageTotal; + } + + @Override + public void setObjectStorageAvailable(String objectStorageAvailable) { + this.objectStorageAvailable = objectStorageAvailable; + } + public void setState(String state) { this.state = state; } @@ -457,4 +589,8 @@ public void setDetails(Map details) { public void setTaggedResourceLimitsAndCounts(List taggedResourceLimitsAndCounts) { this.taggedResources = taggedResourceLimitsAndCounts; } + + public void setTags(Set tags) { + this.tags = tags; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionParameterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionParameterResponse.java new file mode 100644 index 000000000000..d627f8077dc1 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionParameterResponse.java @@ -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. + +package org.apache.cloudstack.api.response; + +import java.util.List; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class ExtensionCustomActionParameterResponse extends BaseResponse { + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the parameter") + private String name; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "Type of the parameter") + private String type; + + @SerializedName(ApiConstants.VALIDATION_FORMAT) + @Param(description = "Validation format for value of the parameter. Available for specific types") + private String validationFormat; + + @SerializedName(ApiConstants.VALUE_OPTIONS) + @Param(description = "Comma-separated list of options for value of the parameter") + private List valueOptions; + + @SerializedName(ApiConstants.REQUIRED) + @Param(description = "Whether the parameter is required or not") + private Boolean required; + + public ExtensionCustomActionParameterResponse(String name, String type, String validationFormat, List valueOptions, + boolean required) { + this.name = name; + this.type = type; + this.validationFormat = validationFormat; + this.valueOptions = valueOptions; + this.required = required; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionResponse.java new file mode 100644 index 000000000000..96edf6d2fd80 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionCustomActionResponse.java @@ -0,0 +1,184 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.extension.ExtensionCustomAction; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ExtensionCustomAction.class) +public class ExtensionCustomActionResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the custom action") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the custom action") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of the custom action") + private String description; + + @SerializedName(ApiConstants.EXTENSION_ID) + @Param(description = "ID of the extension that this custom action belongs to") + private String extensionId; + + @SerializedName(ApiConstants.EXTENSION_NAME) + @Param(description = "Name of the extension that this custom action belongs to") + private String extensionName; + + @SerializedName(ApiConstants.RESOURCE_TYPE) + @Param(description = "Resource type for which the action is available") + private String resourceType; + + @SerializedName(ApiConstants.ALLOWED_ROLE_TYPES) + @Param(description = "List of role types allowed for the custom action") + private List allowedRoleTypes; + + @SerializedName(ApiConstants.SUCCESS_MESSAGE) + @Param(description = "Message that will be used on successful execution of the action") + private String successMessage; + + @SerializedName(ApiConstants.ERROR_MESSAGE) + @Param(description = "Message that will be used on failure during execution of the action") + private String errorMessage; + + @SerializedName(ApiConstants.TIMEOUT) + @Param(description = "Specifies the timeout in seconds to wait for the action to complete before failing") + private Integer timeout; + + @SerializedName(ApiConstants.ENABLED) + @Param(description = "Whether the custom action is enabled or not") + private Boolean enabled; + + @SerializedName(ApiConstants.DETAILS) + @Param(description = "Details of the custom action") + private Map details; + + @SerializedName(ApiConstants.PARAMETERS) + @Param(description = "List of the parameters for the action", responseObject = ExtensionCustomActionParameterResponse.class) + private List parameters; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Creation timestamp of the custom action") + private Date created; + + public ExtensionCustomActionResponse(String id, String name, String description) { + this.id = id; + this.name = name; + this.description = description; + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setExtensionId(String extensionId) { + this.extensionId = extensionId; + } + + public void setExtensionName(String extensionName) { + this.extensionName = extensionName; + } + + public String getResourceType() { + return resourceType; + } + + public void setResourceType(String resourceType) { + this.resourceType = resourceType; + } + + public List getAllowedRoleTypes() { + return allowedRoleTypes; + } + + public void setAllowedRoleTypes(List allowedRoleTypes) { + this.allowedRoleTypes = allowedRoleTypes; + } + + public void setSuccessMessage(String successMessage) { + this.successMessage = successMessage; + } + + public void setErrorMessage(String errorMessage) { + this.errorMessage = errorMessage; + } + + public void setTimeout(Integer timeout) { + this.timeout = timeout; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public void setParameters(List parameters) { + this.parameters = parameters; + } + + public List getParameters() { + return parameters; + } + + public void setDetails(Map details) { + this.details = details; + } + + public Map getDetails() { + return details; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResourceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResourceResponse.java new file mode 100644 index 000000000000..aa370887b748 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResourceResponse.java @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import org.apache.cloudstack.extension.ExtensionResourceMap; +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; + +import java.util.Date; +import java.util.Map; + +@EntityReference(value = ExtensionResourceMap.class) +public class ExtensionResourceResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the resource associated with the extension") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the resource associated with this mapping") + private String name; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "Type of the resource") + private String type; + + @SerializedName(ApiConstants.DETAILS) + @Param(description = "the details of the resource map") + private Map details; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Creation timestamp of the mapping") + private Date created; + + public ExtensionResourceResponse() { + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Map getDetails() { + return details; + } + + public void setDetails(Map details) { + this.details = details; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java new file mode 100644 index 000000000000..911839da405f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ExtensionResponse.java @@ -0,0 +1,192 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.api.Parameter; +import org.apache.cloudstack.extension.Extension; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = Extension.class) +public class ExtensionResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the extension") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the extension") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of the extension") + private String description; + + @SerializedName(ApiConstants.TYPE) + @Param(description = "Type of the extension") + private String type; + + @SerializedName(ApiConstants.PATH) + @Param(description = "The path of the entry point fo the extension") + private String path; + + @SerializedName(ApiConstants.PATH_READY) + @Param(description = "True if the extension path is in ready state across management servers") + private Boolean pathReady; + + @SerializedName(ApiConstants.IS_USER_DEFINED) + @Param(description = "True if the extension is added by admin") + private Boolean userDefined; + + @SerializedName(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM) + @Parameter(description = "Only honored when type is Orchestrator. Whether prepare VM is needed or not") + private Boolean orchestratorRequiresPrepareVm; + + @SerializedName(ApiConstants.STATE) + @Param(description = "The state of the extension") + private String state; + + @SerializedName(ApiConstants.DETAILS) + @Param(description = "The details of the extension") + private Map details; + + @SerializedName(ApiConstants.RESOURCES) + @Param(description = "List of resources to which extension is registered to", responseObject = ExtensionResourceResponse.class) + private List resources; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Creation timestamp of the extension") + private Date created; + + @SerializedName(ApiConstants.REMOVED) + @Param(description = "Removal timestamp of the extension, if applicable") + private Date removed; + + @SerializedName(ApiConstants.RESERVED_RESOURCE_DETAILS) + @Param(description = "Resource detail names as comma separated string that should be reserved and not visible " + + "to end users", + since = "4.22.1") + protected String reservedResourceDetails; + + public ExtensionResponse(String id, String name, String description, String type) { + this.id = id; + this.name = name; + this.description = description; + this.type = type; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public String getType() { + return type; + } + + public String getPath() { + return path; + } + + public Boolean isPathReady() { + return pathReady; + } + + public Boolean isUserDefined() { + return userDefined; + } + + public Boolean isOrchestratorRequiresPrepareVm() { + return orchestratorRequiresPrepareVm; + } + + public String getState() { + return state; + } + + public Map getDetails() { + return details; + } + + public void setPath(String path) { + this.path = path; + } + + public void setPathReady(Boolean pathReady) { + this.pathReady = pathReady; + } + + public void setUserDefined(Boolean userDefined) { + this.userDefined = userDefined; + } + + public void setOrchestratorRequiresPrepareVm(Boolean orchestratorRequiresPrepareVm) { + this.orchestratorRequiresPrepareVm = orchestratorRequiresPrepareVm; + } + + public void setState(String state) { + this.state = state; + } + + public void setDetails(Map details) { + this.details = details; + } + + public List getResources() { + return resources; + } + + public void setResources(List resources) { + this.resources = resources; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public void setReservedResourceDetails(String reservedResourceDetails) { + this.reservedResourceDetails = reservedResourceDetails; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java index 5986c16dc8c0..f6cc9e5d9499 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/FirewallResponse.java @@ -51,6 +51,10 @@ public class FirewallResponse extends BaseResponse { @Param(description = "The Network ID of the firewall rule") private String networkId; + @SerializedName(ApiConstants.VPC_ID) + @Param(description = "The VPC ID of the firewall rule") + private String vpcId; + @SerializedName(ApiConstants.IP_ADDRESS) @Param(description = "The public IP address for the firewall rule") private String publicIpAddress; @@ -115,6 +119,10 @@ public void setNetworkId(String networkId) { this.networkId = networkId; } + public void setVpcId(String vpcId) { + this.vpcId = vpcId; + } + public void setState(String state) { this.state = state; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java index 48097e51d992..aed56a369089 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/FirewallRuleResponse.java @@ -94,6 +94,10 @@ public class FirewallRuleResponse extends BaseResponse { @Param(description = "The ID of the guest Network the port forwarding rule belongs to") private String networkId; + @SerializedName(ApiConstants.NETWORK_NAME) + @Param(description = "The Name of the guest Network the port forwarding rule belongs to") + private String networkName; + @SerializedName(ApiConstants.FOR_DISPLAY) @Param(description = "Is firewall for display to the regular user", since = "4.4", authorized = {RoleType.Admin}) private Boolean forDisplay; @@ -223,6 +227,10 @@ public void setNetworkId(String networkId) { this.networkId = networkId; } + public void setNetworkName(String networkName) { + this.networkName = networkName; + } + public void setForDisplay(Boolean forDisplay) { this.forDisplay = forDisplay; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GetUploadParamsResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GetUploadParamsResponse.java index 1cf769299644..0e51aa6cd381 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GetUploadParamsResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GetUploadParamsResponse.java @@ -62,6 +62,10 @@ public GetUploadParamsResponse() { setObjectName("getuploadparams"); } + public UUID getId() { + return id; + } + public void setId(UUID id) { this.id = id; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java new file mode 100644 index 000000000000..ad91b3490efa --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GpuCardResponse.java @@ -0,0 +1,109 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuCard; + +@EntityReference(value = GpuCard.class) +public class GpuCardResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the GPU card") + protected String id; + + @SerializedName("deviceid") + @Param(description = "the device ID of the GPU card") + protected String deviceId; + + @SerializedName("devicename") + @Param(description = "the device name of the GPU card") + protected String deviceName; + + @SerializedName("name") + @Param(description = "the display name of the GPU card") + protected String name; + + @SerializedName("vendorname") + @Param(description = "the vendor name of the GPU card") + protected String vendorName; + + @SerializedName("vendorid") + @Param(description = "the vendor ID of the GPU card") + protected String vendorId; + + public GpuCardResponse(GpuCard gpuCard) { + super("gpucard"); + id = gpuCard.getUuid(); + deviceId = gpuCard.getDeviceId(); + deviceName = gpuCard.getDeviceName(); + name = gpuCard.getName(); + vendorName = gpuCard.getVendorName(); + vendorId = gpuCard.getVendorId(); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getVendorName() { + return vendorName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + public String getVendorId() { + return vendorId; + } + + public void setVendorId(String vendorId) { + this.vendorId = vendorId; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java new file mode 100644 index 000000000000..09e98b54eaa5 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GpuDeviceResponse.java @@ -0,0 +1,227 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.cloud.vm.VirtualMachine; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuDevice; + +@EntityReference(value = GpuDevice.class) +public class GpuDeviceResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the GPU device") + private String id; + + @SerializedName(ApiConstants.BUS_ADDRESS) + @Param(description = "bus address of the GPU device or MDEV UUID for vGPU devices") + private String bussAddress; + + @SerializedName(ApiConstants.GPU_DEVICE_TYPE) + @Param(description = "bus address of the GPU device") + private GpuDevice.DeviceType type; + + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "the host ID where the GPU device is attached") + private String hostId; + + @SerializedName(ApiConstants.HOST_NAME) + @Param(description = "the host name where the GPU device is attached") + private String hostName; + + @SerializedName(ApiConstants.GPU_CARD_ID) + @Param(description = "the GPU card ID associated with this GPU device") + private String gpuCardId; + + @SerializedName(ApiConstants.GPU_CARD_NAME) + @Param(description = "the GPU card name associated with this GPU device") + private String gpuCardName; + + @SerializedName(ApiConstants.VGPU_PROFILE_ID) + @Param(description = "the vGPU profile ID assigned to this GPU device") + private String vgpuProfileId; + + @SerializedName(ApiConstants.VGPU_PROFILE_NAME) + @Param(description = "the vGPU profile name assigned to this GPU device") + private String vgpuProfileName; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "the vGPU profile ID assigned to this GPU device") + private String vmId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_NAME) + @Param(description = "the vGPU profile name assigned to this GPU device") + private String vmName; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_STATE) + @Param(description = "the state of the virtual machine to which this GPU device is allocated") + private VirtualMachine.State vmState; + + @SerializedName(ApiConstants.STATE) + @Param(description = "the vGPU profile name assigned to this GPU device") + private GpuDevice.State state; + + @SerializedName(ApiConstants.MANAGED_STATE) + @Param(description = "the managed state of the GPU device (Enabled/Disabled)") + private GpuDevice.ManagedState managedState; + + @SerializedName(ApiConstants.PARENT_GPU_DEVICE_ID) + @Param(description = "the ID of the parent GPU device, if this is a vGPU") + private String parentGpuDeviceId; + + @SerializedName(ApiConstants.NUMA_NODE) + @Param(description = "the NUMA node where the GPU device is located") + private String numaNode; + + + public GpuDeviceResponse() { + // Empty constructor for serialization + super("gpudevice"); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getBussAddress() { + return bussAddress; + } + + public void setBussAddress(String bussAddress) { + this.bussAddress = bussAddress; + } + + public GpuDevice.DeviceType getType() { + return type; + } + + public void setType(GpuDevice.DeviceType type) { + this.type = type; + } + + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } + + public String getGpuCardId() { + return gpuCardId; + } + + public void setGpuCardId(String gpuCardId) { + this.gpuCardId = gpuCardId; + } + + public String getGpuCardName() { + return gpuCardName; + } + + public void setGpuCardName(String gpuCardName) { + this.gpuCardName = gpuCardName; + } + + public String getVgpuProfileId() { + return vgpuProfileId; + } + + public void setVgpuProfileId(String vgpuProfileId) { + this.vgpuProfileId = vgpuProfileId; + } + + public String getVgpuProfileName() { + return vgpuProfileName; + } + + public void setVgpuProfileName(String vgpuProfileName) { + this.vgpuProfileName = vgpuProfileName; + } + + public String getVmId() { + return vmId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public VirtualMachine.State getVmState() { + return vmState; + } + + public void setVmState(VirtualMachine.State vmState) { + this.vmState = vmState; + } + + public GpuDevice.State getState() { + return state; + } + + public void setState(GpuDevice.State state) { + this.state = state; + } + + public GpuDevice.ManagedState getManagedState() { + return managedState; + } + + public void setManagedState(GpuDevice.ManagedState managedState) { + this.managedState = managedState; + } + + public String getParentGpuDeviceId() { + return parentGpuDeviceId; + } + + public void setParentGpuDeviceId(String parentGpuDeviceId) { + this.parentGpuDeviceId = parentGpuDeviceId; + } + + public String getNumaNode() { + return numaNode; + } + + public void setNumaNode(String numaNode) { + this.numaNode = numaNode; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java index c3ef28d2d5d3..182e74200070 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuestOSCategoryResponse.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.api.response; +import java.util.Date; + import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.ApiConstants; @@ -26,7 +28,7 @@ import com.cloud.storage.GuestOsCategory; @EntityReference(value = GuestOsCategory.class) -public class GuestOSCategoryResponse extends BaseResponse { +public class GuestOSCategoryResponse extends BaseResponse implements SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "The ID of the OS category") private String id; @@ -35,6 +37,18 @@ public class GuestOSCategoryResponse extends BaseResponse { @Param(description = "The name of the OS category") private String name; + @SerializedName(ApiConstants.IS_FEATURED) + @Param(description = "Whether the OS category is featured", since = "4.21.0") + private Boolean featured; + + @SerializedName(ApiConstants.RESOURCE_ICON) + @Param(description = "Base64 string representation of the resource icon", since = "4.21.0") + private ResourceIconResponse resourceIconResponse; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Date when the OS category was created." ) + private Date created; + public String getId() { return id; } @@ -50,4 +64,17 @@ public String getName() { public void setName(String name) { this.name = name; } + + public void setFeatured(Boolean featured) { + this.featured = featured; + } + + @Override + public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) { + this.resourceIconResponse = resourceIconResponse; + } + + public void setCreated(Date created) { + this.created = created; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java new file mode 100644 index 000000000000..3c53bdb9d93d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/GuiThemeResponse.java @@ -0,0 +1,192 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gui.theme.GuiThemeJoin; + +import java.util.Date; + +@EntityReference(value = {GuiThemeJoin.class}) +public class GuiThemeResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the custom GUI theme.") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the GUI theme.") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of the GUI theme.") + private String description; + + @SerializedName(ApiConstants.CSS) + @Param(description = "The CSS to be retrieved and imported into the GUI when matching the theme access configurations.") + private String css; + + @SerializedName(ApiConstants.JSON_CONFIGURATION) + @Param(description = "The JSON with the configurations to be retrieved and imported into the GUI when matching the theme access configurations.") + private String jsonConfiguration; + + @SerializedName(ApiConstants.COMMON_NAMES) + @Param(description = "A set of Common Names (CN) (fixed or wildcard) separated by comma that can retrieve the theme; e.g.: *acme.com,acme2.com") + private String commonNames; + + @SerializedName(ApiConstants.LOGIN_BASE_DOMAIN) + @Param(description = "The ACS domain to be used as base for the login when accessing the GUI through the common name defined in the theme. If a " + + "common name is not defined, this parameter is ignored on the GUI.") + private String loginBaseDomain; + + @SerializedName(ApiConstants.DOMAIN_IDS) + @Param(description = "A set of domain UUIDs (also known as ID for the end-user) separated by comma that can retrieve the theme.") + private String domainIds; + + @SerializedName(ApiConstants.RECURSIVE_DOMAINS) + @Param(description = "Whether to consider the subdomains of the informed domain IDs.") + private Boolean recursiveDomains; + + @SerializedName(ApiConstants.ACCOUNT_IDS) + @Param(description = "A set of account UUIDs (also known as ID for the end-user) separated by comma that can retrieve the theme.") + private String accountIds; + + @SerializedName(ApiConstants.IS_PUBLIC) + @Param(description = "Defines whether a theme can be retrieved by anyone when only the `commonNames` is informed. If the `domainIds` or `accountIds` is informed, it is " + + "considered as `false`.") + private Boolean isPublic; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "When the GUI theme was created.") + private Date created; + + @SerializedName(ApiConstants.REMOVED) + @Param(description = "When the GUI theme was removed.") + private Date removed; + + public GuiThemeResponse() { + super("guiThemes"); + } + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getCss() { + return css; + } + + public void setCss(String css) { + this.css = css; + } + + public String getJsonConfiguration() { + return jsonConfiguration; + } + + public void setJsonConfiguration(String jsonConfiguration) { + this.jsonConfiguration = jsonConfiguration; + } + + public String getCommonNames() { + return commonNames; + } + + public void setCommonNames(String commonNames) { + this.commonNames = commonNames; + } + + public String getDomainIds() { + return domainIds; + } + + public void setDomainIds(String domainIds) { + this.domainIds = domainIds; + } + + public String getAccountIds() { + return accountIds; + } + + public void setAccountIds(String accountIds) { + this.accountIds = accountIds; + } + + public Boolean getPublic() { + return isPublic; + } + + public void setPublic(Boolean aPublic) { + isPublic = aPublic; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public Boolean getRecursiveDomains() { + return recursiveDomains; + } + + public void setRecursiveDomains(Boolean recursiveDomains) { + this.recursiveDomains = recursiveDomains; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public String getLoginBaseDomain() { + return loginBaseDomain; + } + + public void setLoginBaseDomain(String loginBaseDomain) { + this.loginBaseDomain = loginBaseDomain; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HSMProfileResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HSMProfileResponse.java new file mode 100644 index 000000000000..7576c76bef15 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/HSMProfileResponse.java @@ -0,0 +1,254 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.kms.HSMProfile; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = HSMProfile.class) +public class HSMProfileResponse extends BaseResponse implements ControlledViewEntityResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the HSM profile") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the HSM profile") + private String name; + + @SerializedName(ApiConstants.PROTOCOL) + @Param(description = "the protocol of the HSM profile") + private String protocol; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "the account ID of the HSM profile owner") + private String accountId; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account name of the HSM profile owner") + private String accountName; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the domain ID of the HSM profile owner") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the domain name of the HSM profile owner") + private String domainName; + + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path of the HSM profile owner") + private String domainPath; + + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "the project ID of the HSM profile owner") + private String projectId; + + @SerializedName(ApiConstants.PROJECT) + @Param(description = "the project name of the HSM profile owner") + private String projectName; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "the zone ID where the HSM profile is available") + private String zoneId; + + @SerializedName(ApiConstants.ZONE_NAME) + @Param(description = "the zone name where the HSM profile is available") + private String zoneName; + + @SerializedName("vendor") + @Param(description = "the vendor name of the HSM profile") + private String vendorName; + + @SerializedName(ApiConstants.STATE) + @Param(description = "the state of the HSM profile") + private String state; + + @SerializedName(ApiConstants.ENABLED) + @Param(description = "whether the HSM profile is enabled") + private Boolean enabled; + + @SerializedName(ApiConstants.IS_PUBLIC) + @Param(description = "whether this is a system HSM profile available to all users globally") + private Boolean isPublic; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the date the HSM profile was created") + private Date created; + + @SerializedName(ApiConstants.DETAILS) + @Param(description = "HSM configuration details (sensitive values are encrypted)") + private Map details; + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + @Override + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + @Override + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + @Override + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + @Override + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + @Override + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public void setZoneName(String zoneName) { + this.zoneName = zoneName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + public void setState(String state) { + this.state = state; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public void setIsPublic(Boolean isPublic) { + this.isPublic = isPublic; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setDetails(Map details) { + this.details = details; + } + + public String getId() { + return id; + } + + public String getName() { + return name; + } + + public String getProtocol() { + return protocol; + } + + public String getAccountId() { + return accountId; + } + + public String getAccountName() { + return accountName; + } + + public String getDomainId() { + return domainId; + } + + public String getDomainName() { + return domainName; + } + + public String getDomainPath() { + return domainPath; + } + + public String getProjectId() { + return projectId; + } + + public String getProjectName() { + return projectName; + } + + public String getZoneId() { + return zoneId; + } + + public String getZoneName() { + return zoneName; + } + + public String getVendorName() { + return vendorName; + } + + public String getState() { + return state; + } + + public Boolean getEnabled() { + return enabled; + } + + public Boolean getPublic() { + return isPublic; + } + + public Date getCreated() { + return created; + } + + public Map getDetails() { + return details; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java index 668c7b8c699e..10bd62804fb2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/HostResponse.java @@ -63,6 +63,10 @@ public class HostResponse extends BaseResponseWithAnnotations { @Param(description = "The OS category name of the host") private String osCategoryName; + @SerializedName(ApiConstants.GUEST_OS_RULE) + @Param(description = "the guest OS rule") + private String guestOsRule; + @SerializedName(ApiConstants.IP_ADDRESS) @Param(description = "The IP address of the host") private String ipAddress; @@ -90,7 +94,6 @@ public class HostResponse extends BaseResponseWithAnnotations { @SerializedName(ApiConstants.HYPERVISOR) @Param(description = "The host hypervisor") private String hypervisor; - @SerializedName("cpusockets") @Param(description = "The number of CPU sockets on the host") private Integer cpuSockets; @@ -166,6 +169,14 @@ public class HostResponse extends BaseResponseWithAnnotations { @Param(description = "The amount of the host's memory currently used") private Long memoryUsed; + @SerializedName("gputotal") + @Param(description = "Total GPUs on the Host", responseObject = Long.class, since = "4.21") + private Long gpuTotal; + + @SerializedName("gpuused") + @Param(description = "Used GPUs on the Host", responseObject = Long.class, since = "4.21") + private Long gpuUsed; + @SerializedName(ApiConstants.GPUGROUP) @Param(description = "GPU cards present in the host", responseObject = GpuResponse.class, since = "4.4") private List gpuGroup; @@ -186,10 +197,24 @@ public class HostResponse extends BaseResponseWithAnnotations { @Param(description = "The date and time the host was last pinged") private Date lastPinged; - @SerializedName("managementserverid") + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "the virtual machine id for host type ConsoleProxy and SecondaryStorageVM", since = "4.21.0") + private String virtualMachineId; + + @SerializedName("msid") + @Param(description = "(only for details=core) the msid of the host's management server") + private Long msId; + + @SerializedName(ApiConstants.MANAGEMENT_SERVER_ID) @Param(description = "The management server ID of the host") private String managementServerId; + @SerializedName(ApiConstants.MANAGEMENT_SERVER_NAME) + @Param(description = "the management server name of the host", since = "4.21.0") + private String managementServerName; + + private transient long clusterInternalId; + @SerializedName("clusterid") @Param(description = "The cluster ID of the host") private String clusterId; @@ -294,6 +319,30 @@ public class HostResponse extends BaseResponseWithAnnotations { @Param(description = "CPU Arch of the host", since = "4.20") private String arch; + @SerializedName(ApiConstants.STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups for the host", since = "4.21.0") + private String storageAccessGroups; + + @SerializedName(ApiConstants.CLUSTER_STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups on the cluster", since = "4.21.0") + private String clusterStorageAccessGroups; + + @SerializedName(ApiConstants.POD_STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups on the pod", since = "4.21.0") + private String podStorageAccessGroups; + + @SerializedName(ApiConstants.ZONE_STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups on the zone", since = "4.21.0") + private String zoneStorageAccessGroups; + + @SerializedName(ApiConstants.EXTENSION_ID) + @Param(description="The ID of extension for this cluster", since = "4.21.0") + private String extensionId; + + @SerializedName(ApiConstants.EXTENSION_NAME) + @Param(description="The name of extension for this cluster", since = "4.21.0") + private String extensionName; + @Override public String getObjectId() { return this.getId(); @@ -415,6 +464,14 @@ public void setMemoryUsed(Long memoryUsed) { this.memoryUsed = memoryUsed; } + public void setGpuTotal(Long gpuTotal) { + this.gpuTotal = gpuTotal; + } + + public void setGpuUsed(Long gpuUsed) { + this.gpuUsed = gpuUsed; + } + public void setGpuGroup(List gpuGroup) { this.gpuGroup = gpuGroup; } @@ -435,10 +492,34 @@ public void setLastPinged(Date lastPinged) { this.lastPinged = lastPinged; } + public void setVirtualMachineId(String virtualMachineId) { + this.virtualMachineId = virtualMachineId; + } + + public Long getMsId() { + return msId; + } + + public void setMsId(Long msId) { + this.msId = msId; + } + public void setManagementServerId(String managementServerId) { this.managementServerId = managementServerId; } + public void setManagementServerName(String managementServerName) { + this.managementServerName = managementServerName; + } + + public long getClusterInternalId() { + return clusterInternalId; + } + + public void setClusterInternalId(long clusterInternalId) { + this.clusterInternalId = clusterInternalId; + } + public void setClusterId(String clusterId) { this.clusterId = clusterId; } @@ -475,6 +556,38 @@ public void setHostTags(String hostTags) { this.hostTags = hostTags; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + + public String getClusterStorageAccessGroups() { + return clusterStorageAccessGroups; + } + + public void setClusterStorageAccessGroups(String clusterStorageAccessGroups) { + this.clusterStorageAccessGroups = clusterStorageAccessGroups; + } + + public String getPodStorageAccessGroups() { + return podStorageAccessGroups; + } + + public void setPodStorageAccessGroups(String podStorageAccessGroups) { + this.podStorageAccessGroups = podStorageAccessGroups; + } + + public String getZoneStorageAccessGroups() { + return zoneStorageAccessGroups; + } + + public void setZoneStorageAccessGroups(String zoneStorageAccessGroups) { + this.zoneStorageAccessGroups = zoneStorageAccessGroups; + } + public String getExplicitHostTags() { return explicitHostTags; } @@ -723,10 +836,18 @@ public Date getLastPinged() { return lastPinged; } + public String getVirtualMachineId() { + return virtualMachineId; + } + public String getManagementServerId() { return managementServerId; } + public String getManagementServerName() { + return managementServerName; + } + public String getClusterId() { return clusterId; } @@ -831,6 +952,14 @@ public Long getMemoryAllocatedBytes() { return memoryAllocatedBytes; } + public Long getGpuTotal() { + return gpuTotal; + } + + public Long getGpuUsed() { + return gpuUsed; + } + public Boolean getTagARule() { return isTagARule; } @@ -870,4 +999,28 @@ public Boolean getEncryptionSupported() { public Boolean getInstanceConversionSupported() { return instanceConversionSupported; } + + public void setExtensionId(String extensionId) { + this.extensionId = extensionId; + } + + public String getExtensionId() { + return extensionId; + } + + public void setExtensionName(String extensionName) { + this.extensionName = extensionName; + } + + public String getExtensionName() { + return extensionName; + } + + public String getGuestOsRule() { + return guestOsRule; + } + + public void setGuestOsRule(String guestOsRule) { + this.guestOsRule = guestOsRule; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java index 0f6d9038fac2..cdc3115c8293 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/IPAddressResponse.java @@ -175,6 +175,10 @@ public class IPAddressResponse extends BaseResponseWithAnnotations implements Co @Param(description="true if range is dedicated for System VMs") private boolean forSystemVms; + @SerializedName(ApiConstants.FOR_PROVIDER) + @Param(description="true if range is dedicated for external network providers", since = "4.21.0") + private boolean forProvider; + public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } @@ -332,4 +336,8 @@ public void setHasRules(final boolean hasRules) { public void setForSystemVms(boolean forSystemVms) { this.forSystemVms = forSystemVms; } + + public void setForProvider(boolean forProvider) { + this.forProvider = forProvider; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java new file mode 100644 index 000000000000..15576e8f1012 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ImageTransferResponse.java @@ -0,0 +1,104 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.api.response; + +import java.util.Date; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.backup.ImageTransfer; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ImageTransfer.class) +public class ImageTransferResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the image transfer") + private String id; + + @SerializedName("backupid") + @Param(description = "the backup ID") + private String backupId; + + @SerializedName("vmid") + @Param(description = "the VM ID") + private String vmId; + + @SerializedName(ApiConstants.VOLUME_ID) + @Param(description = "the disk/volume ID") + private String diskId; + + @SerializedName("devicename") + @Param(description = "the device name (vda, vdb, etc)") + private String deviceName; + + @SerializedName("transferurl") + @Param(description = "the transfer URL") + private String transferUrl; + + @SerializedName("phase") + @Param(description = "the transfer phase") + private String phase; + + @SerializedName("direction") + @Param(description = "the image transfer direction: upload / download") + private String direction; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the date created") + private Date created; + + public void setId(String id) { + this.id = id; + } + + public void setBackupId(String backupId) { + this.backupId = backupId; + } + + public void setVmId(String vmId) { + this.vmId = vmId; + } + + public void setDiskId(String diskId) { + this.diskId = diskId; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } + + public void setPhase(String phase) { + this.phase = phase; + } + + public void setDirection(String direction) { + this.direction = direction; + } + + public void setCreated(Date created) { + this.created = created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ImportVMTaskResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ImportVMTaskResponse.java new file mode 100644 index 000000000000..aa85554f5678 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ImportVMTaskResponse.java @@ -0,0 +1,257 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import java.util.Date; + +public class ImportVMTaskResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of importing task") + private String id; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "the Zone ID") + private String zoneId; + + @SerializedName(ApiConstants.ZONE_NAME) + @Param(description = "the Zone name") + private String zoneName; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account name") + private String accountName; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "the ID of account") + private String accountId; + + @SerializedName(ApiConstants.VIRTUAL_MACHINE_ID) + @Param(description = "the ID of the imported VM (after task is completed)") + private String virtualMachineId; + + @SerializedName(ApiConstants.DISPLAY_NAME) + @Param(description = "the display name of the importing VM") + private String displayName; + + @SerializedName(ApiConstants.STATE) + @Param(description = "the state of the importing VM task") + private String state; + + @SerializedName(ApiConstants.VCENTER) + @Param(description = "the vcenter name of the importing VM task") + private String vcenter; + + @SerializedName(ApiConstants.DATACENTER_NAME) + @Param(description = "the datacenter name of the importing VM task") + private String datacenterName; + + @SerializedName("sourcevmname") + @Param(description = "the source VM name") + private String sourceVMName; + + @SerializedName("step") + @Param(description = "the current step on the importing VM task") + private String step; + + @SerializedName("stepduration") + @Param(description = "the duration of the current step") + private String stepDuration; + + @SerializedName(ApiConstants.DURATION) + @Param(description = "the total task duration") + private String duration; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the current step description on the importing VM task") + private String description; + + @SerializedName(ApiConstants.CONVERT_INSTANCE_HOST_ID) + @Param(description = "the ID of the host on which the instance is being converted") + private String convertInstanceHostId; + + @SerializedName("convertinstancehostname") + @Param(description = "the name of the host on which the instance is being converted") + private String convertInstanceHostName; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the create date of the importing task") + private Date created; + + @SerializedName(ApiConstants.LAST_UPDATED) + @Param(description = "the last updated date of the importing task") + private Date lastUpdated; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getZoneName() { + return zoneName; + } + + public void setZoneName(String zoneName) { + this.zoneName = zoneName; + } + + public String getAccountName() { + return accountName; + } + + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getVirtualMachineId() { + return virtualMachineId; + } + + public void setVirtualMachineId(String virtualMachineId) { + this.virtualMachineId = virtualMachineId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getVcenter() { + return vcenter; + } + + public void setVcenter(String vcenter) { + this.vcenter = vcenter; + } + + public String getDatacenterName() { + return datacenterName; + } + + public void setDatacenterName(String datacenterName) { + this.datacenterName = datacenterName; + } + + public String getSourceVMName() { + return sourceVMName; + } + + public void setSourceVMName(String sourceVMName) { + this.sourceVMName = sourceVMName; + } + + public String getStep() { + return step; + } + + public void setStep(String step) { + this.step = step; + } + + public String getStepDuration() { + return stepDuration; + } + + public void setStepDuration(String stepDuration) { + this.stepDuration = stepDuration; + } + + public String getDuration() { + return duration; + } + + public void setDuration(String duration) { + this.duration = duration; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getConvertInstanceHostId() { + return convertInstanceHostId; + } + + public void setConvertInstanceHostId(String convertInstanceHostId) { + this.convertInstanceHostId = convertInstanceHostId; + } + + public String getConvertInstanceHostName() { + return convertInstanceHostName; + } + + public void setConvertInstanceHostName(String convertInstanceHostName) { + this.convertInstanceHostName = convertInstanceHostName; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getLastUpdated() { + return lastUpdated; + } + + public void setLastUpdated(Date lastUpdated) { + this.lastUpdated = lastUpdated; + } + + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/KMSKeyResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/KMSKeyResponse.java new file mode 100644 index 000000000000..cd7a360a497c --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/KMSKeyResponse.java @@ -0,0 +1,272 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.acl.RoleType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.kms.KMSKey; + +import java.util.Date; + +@EntityReference(value = KMSKey.class) +public class KMSKeyResponse extends BaseResponse implements ControlledViewEntityResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "the UUID of the key") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the key") + private String name; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the key") + private String description; + + @SerializedName(ApiConstants.ACCOUNT) + @Param(description = "the account owning the key") + private String accountName; + + @SerializedName(ApiConstants.ACCOUNT_ID) + @Param(description = "the account ID owning the key") + private String accountId; + + @SerializedName(ApiConstants.DOMAIN_ID) + @Param(description = "the domain ID of the key") + private String domainId; + + @SerializedName(ApiConstants.DOMAIN) + @Param(description = "the domain name of the key") + private String domainName; + + @SerializedName(ApiConstants.DOMAIN_PATH) + @Param(description = "the domain path of the key") + private String domainPath; + + @SerializedName(ApiConstants.ZONE_ID) + @Param(description = "the zone ID where the key is valid") + private String zoneId; + + @SerializedName(ApiConstants.ZONE_NAME) + @Param(description = "the zone name where the key is valid") + private String zoneName; + + @SerializedName(ApiConstants.HSM_PROFILE_ID) + @Param(description = "the zone ID where the key is valid") + private String hsmProfileId; + + @SerializedName(ApiConstants.HSM_PROFILE) + @Param(description = "the zone name where the key is valid") + private String hsmProfileName; + + @SerializedName(ApiConstants.ALGORITHM) + @Param(description = "the encryption algorithm") + private String algorithm; + + @SerializedName(ApiConstants.KEY_BITS) + @Param(description = "the key size in bits") + private Integer keyBits; + + @SerializedName(ApiConstants.VERSION) + @Param(description = "the key size in bits") + private Integer version; + + @SerializedName(ApiConstants.ENABLED) + @Param(description = "whether the key is enabled") + private Boolean enabled; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "the creation timestamp") + private Date created; + + @SerializedName(ApiConstants.PROJECT_ID) + @Param(description = "the project ID of the key") + private String projectId; + + @SerializedName(ApiConstants.PROJECT) + @Param(description = "the project name of the key") + private String projectName; + + @SerializedName(ApiConstants.KEK_LABEL) + @Param(description = "the provider-specific KEK label (admin only)", authorized = { RoleType.Admin }) + private String kekLabel; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getAccountName() { + return accountName; + } + + @Override + public void setAccountName(String accountName) { + this.accountName = accountName; + } + + @Override + public void setProjectId(String projectId) { + this.projectId = projectId; + } + + @Override + public void setProjectName(String projectName) { + this.projectName = projectName; + } + + public String getAccountId() { + return accountId; + } + + public void setAccountId(String accountId) { + this.accountId = accountId; + } + + public String getDomainId() { + return domainId; + } + + @Override + public void setDomainId(String domainId) { + this.domainId = domainId; + } + + public String getDomainName() { + return domainName; + } + + @Override + public void setDomainName(String domainName) { + this.domainName = domainName; + } + + public String getDomainPath() { + return domainPath; + } + + @Override + public void setDomainPath(String domainPath) { + this.domainPath = domainPath; + } + + public String getZoneId() { + return zoneId; + } + + public void setZoneId(String zoneId) { + this.zoneId = zoneId; + } + + public String getZoneName() { + return zoneName; + } + + public void setZoneName(String zoneName) { + this.zoneName = zoneName; + } + + public String getHsmProfileId() { + return hsmProfileId; + } + + public void setHsmProfileId(String hsmProfileId) { + this.hsmProfileId = hsmProfileId; + } + + public String getHsmProfileName() { + return hsmProfileName; + } + + public void setHsmProfileName(String hsmProfileName) { + this.hsmProfileName = hsmProfileName; + } + + public String getAlgorithm() { + return algorithm; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public Integer getKeyBits() { + return keyBits; + } + + public void setKeyBits(Integer keyBits) { + this.keyBits = keyBits; + } + + public Integer getVersion() { + return version; + } + + public void setVersion(Integer version) { + this.version = version; + } + + public Boolean getEnabled() { + return enabled; + } + + public void setEnabled(Boolean enabled) { + this.enabled = enabled; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getKekLabel() { + return kekLabel; + } + + public void setKekLabel(String kekLabel) { + this.kekLabel = kekLabel; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/KubernetesUserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/KubernetesUserVmResponse.java new file mode 100644 index 000000000000..cef5cdae2f45 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/KubernetesUserVmResponse.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.network.router.VirtualRouter; +import com.cloud.serializer.Param; +import com.cloud.uservm.UserVm; +import com.cloud.vm.VirtualMachine; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; + +@EntityReference(value = {VirtualMachine.class, UserVm.class, VirtualRouter.class}) +public class KubernetesUserVmResponse extends UserVmResponse { + @SerializedName(ApiConstants.IS_EXTERNAL_NODE) + @Param(description = "If the VM is an externally added node") + private boolean isExternalNode; + + @SerializedName(ApiConstants.IS_ETCD_NODE) + @Param(description = "If the VM is an etcd node") + private boolean isEtcdNode; + + @SerializedName(ApiConstants.KUBERNETES_NODE_VERSION) + @Param(description = "Kubernetes version of the node") + private String nodeVersion; + + + public void setExternalNode(boolean externalNode) { + isExternalNode = externalNode; + } + + public void setEtcdNode(boolean etcdNode) { + isEtcdNode = etcdNode; + } + + public void setNodeVersion(String nodeVersion) { this.nodeVersion = nodeVersion;} +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java index 4edcab2e577a..6e3ef4678d28 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/LoginCmdResponse.java @@ -86,6 +86,14 @@ public class LoginCmdResponse extends AuthenticationCmdResponse { @Param(description = "Two factor authentication issuer", since = "4.18.0.0") private String issuerFor2FA; + @SerializedName(value = ApiConstants.MANAGEMENT_SERVER_ID) + @Param(description = "Management Server ID that the user logged to", since = "4.21.0.0") + private String managementServerId; + + @SerializedName(value = ApiConstants.PASSWORD_CHANGE_REQUIRED) + @Param(description = "Indicates whether the User is required to change password on next login.", since = "4.23.0") + private Boolean passwordChangeRequired; + public String getUsername() { return username; } @@ -211,4 +219,20 @@ public String getIssuerFor2FA() { public void setIssuerFor2FA(String issuerFor2FA) { this.issuerFor2FA = issuerFor2FA; } + + public String getManagementServerId() { + return managementServerId; + } + + public void setManagementServerId(String managementServerId) { + this.managementServerId = managementServerId; + } + + public Boolean getPasswordChangeRequired() { + return passwordChangeRequired; + } + + public void setPasswordChangeRequired(Boolean passwordChangeRequired) { + this.passwordChangeRequired = passwordChangeRequired; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java index 077b3c349432..c1f24c63230d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ManagementServerResponse.java @@ -87,6 +87,22 @@ public class ManagementServerResponse extends BaseResponse { @Param(description = "the Management Server Peers") private List peers; + @SerializedName(ApiConstants.LAST_AGENTS) + @Param(description = "the last agents this Management Server is responsible for, before shutdown or preparing for maintenance", since = "4.21.0.0") + private List lastAgents; + + @SerializedName(ApiConstants.AGENTS) + @Param(description = "the agents this Management Server is responsible for", since = "4.21.0.0") + private List agents; + + @SerializedName(ApiConstants.AGENTS_COUNT) + @Param(description = "the number of host agents this Management Server is responsible for", since = "4.21.0.0") + private Long agentsCount; + + @SerializedName(ApiConstants.PENDING_JOBS_COUNT) + @Param(description = "the number of pending jobs in this Management Server", since = "4.21.0.0") + private Long pendingJobsCount; + public String getId() { return this.id; } @@ -135,6 +151,22 @@ public String getIpAddress() { return ipAddress; } + public List getLastAgents() { + return lastAgents; + } + + public List getAgents() { + return agents; + } + + public Long getAgentsCount() { + return this.agentsCount; + } + + public Long getPendingJobsCount() { + return this.pendingJobsCount; + } + public void setId(String id) { this.id = id; } @@ -187,6 +219,22 @@ public void setIpAddress(String ipAddress) { this.ipAddress = ipAddress; } + public void setLastAgents(List lastAgents) { + this.lastAgents = lastAgents; + } + + public void setAgents(List agents) { + this.agents = agents; + } + + public void setAgentsCount(Long agentsCount) { + this.agentsCount = agentsCount; + } + + public void setPendingJobsCount(Long pendingJobsCount) { + this.pendingJobsCount = pendingJobsCount; + } + public String getKernelVersion() { return kernelVersion; } 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 faa257ccfa72..aeaf333540c7 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 @@ -211,14 +211,6 @@ public class NetworkResponse extends BaseResponseWithAssociatedNetwork implement @Param(description = "Name of the VPC to which this Network belongs", since = "4.15") private String vpcName; - @SerializedName(ApiConstants.ASSOCIATED_NETWORK_ID) - @Param(description = "The ID of the Network associated with this Network") - private String associatedNetworkId; - - @SerializedName(ApiConstants.ASSOCIATED_NETWORK) - @Param(description = "The name of the Network associated with this Network") - private String associatedNetworkName; - @SerializedName(ApiConstants.TUNGSTEN_VIRTUAL_ROUTER_UUID) @Param(description = "Tungsten-Fabric virtual router the Network belongs to") private String tungstenVirtualRouterUuid; @@ -339,6 +331,18 @@ 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; + + @SerializedName(ApiConstants.DNS_ZONE) + @Param(description = "DNS zone associated to the network", since = "4.23.0") + private String dnsZone; + + @SerializedName(ApiConstants.DNS_SUB_DOMAIN) + @Param(description = "DNS subdomain associated to the network", since = "4.23.0") + private String dnsSubdomain; + public NetworkResponse() {} public Boolean getDisplayNetwork() { @@ -619,14 +623,6 @@ public void setVpcName(String vpcName) { this.vpcName = vpcName; } - public void setAssociatedNetworkId(String associatedNetworkId) { - this.associatedNetworkId = associatedNetworkId; - } - - public void setAssociatedNetworkName(String associatedNetworkName) { - this.associatedNetworkName = associatedNetworkName; - } - @Override public void setResourceIconResponse(ResourceIconResponse icon) { this.icon = icon; @@ -718,4 +714,16 @@ public void setIpv6Dns1(String ipv6Dns1) { public void setIpv6Dns2(String ipv6Dns2) { this.ipv6Dns2 = ipv6Dns2; } + + public void setKeepMacAddressOnPublicNic(Boolean keepMacAddressOnPublicNic) { + this.keepMacAddressOnPublicNic = keepMacAddressOnPublicNic; + } + + public void setDnsZone(String dnsZone) { + this.dnsZone = dnsZone; + } + + public void setDnsSubdomain(String dnsSubdomain) { + this.dnsSubdomain = dnsSubdomain; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java index f992514b8db2..5ad41ad62244 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NicResponse.java @@ -146,6 +146,14 @@ public class NicResponse extends BaseResponse { @Param(description = "Public IP address associated with this NIC via Static NAT rule") private String publicIp; + @SerializedName(ApiConstants.ENABLED) + @Param(description = "whether the NIC is enabled or not") + private Boolean isEnabled; + + @SerializedName(ApiConstants.NIC_DNS_NAME) + @Param(description = "DNS name associated with this NIC's IP address") + private String nicDnsName; + public void setVmId(String vmId) { this.vmId = vmId; } @@ -416,4 +424,16 @@ public void setPublicIpId(String publicIpId) { public void setPublicIp(String publicIp) { this.publicIp = publicIp; } + + public Boolean getEnabled() { + return isEnabled; + } + + public void setEnabled(Boolean enabled) { + isEnabled = enabled; + } + + public void setNicDnsName(String nicDnsName) { + this.nicDnsName = nicDnsName; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/NicSecondaryIpResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/NicSecondaryIpResponse.java index eba00da25cfe..8734e65dfa58 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/NicSecondaryIpResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/NicSecondaryIpResponse.java @@ -53,6 +53,10 @@ public class NicSecondaryIpResponse extends BaseResponse { @Param(description = "The ID of the Instance") private String vmId; + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of the secondary IP address") + private String description; + @Override public String getObjectId() { return this.getId(); @@ -98,6 +102,14 @@ public void setId(String id) { this.id = id; } + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + public List getSecondaryIpsList() { return secondaryIpsList; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ObjectStoreResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ObjectStoreResponse.java index e4030799aa79..dcb93aaaf1d2 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ObjectStoreResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ObjectStoreResponse.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.api.response; import com.cloud.serializer.Param; + +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.storage.object.ObjectStore; import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.api.BaseResponseWithAnnotations; @@ -24,15 +26,15 @@ @EntityReference(value = ObjectStore.class) public class ObjectStoreResponse extends BaseResponseWithAnnotations { - @SerializedName("id") + @SerializedName(ApiConstants.ID) @Param(description = "the ID of the object store") private String id; - @SerializedName("name") + @SerializedName(ApiConstants.NAME) @Param(description = "the name of the object store") private String name; - @SerializedName("url") + @SerializedName(ApiConstants.URL) @Param(description = "the url of the object store") private String url; @@ -44,6 +46,10 @@ public class ObjectStoreResponse extends BaseResponseWithAnnotations { @Param(description = "the total size of the object store") private Long storageTotal; + @SerializedName("storageallocated") + @Param(description = "the allocated size of the object store") + private Long storageAllocated; + @SerializedName("storageused") @Param(description = "the object store currently used size") private Long storageUsed; @@ -96,6 +102,14 @@ public void setStorageTotal(Long storageTotal) { this.storageTotal = storageTotal; } + public Long getStorageAllocated() { + return storageAllocated; + } + + public void setStorageAllocated(Long storageAllocated) { + this.storageAllocated = storageAllocated; + } + public Long getStorageUsed() { return storageUsed; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java index 2a4a33b2e460..51cad6381f73 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/PodResponse.java @@ -85,6 +85,14 @@ public class PodResponse extends BaseResponseWithAnnotations { @Param(description = "The capacity of the Pod", responseObject = CapacityResponse.class) private List capacities; + @SerializedName(ApiConstants.STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups for the pod", since = "4.21.0") + private String storageAccessGroups; + + @SerializedName(ApiConstants.ZONE_STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups on the zone", since = "4.21.0") + private String zoneStorageAccessGroups; + public String getId() { return id; } @@ -184,4 +192,20 @@ public List getCapacities() { public void setCapacities(List capacities) { this.capacities = capacities; } + + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + + public String getZoneStorageAccessGroups() { + return zoneStorageAccessGroups; + } + + public void setZoneStorageAccessGroups(String zoneStorageAccessGroups) { + this.zoneStorageAccessGroups = zoneStorageAccessGroups; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java index ee40e9d39188..40f9405d0fc5 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ProjectResponse.java @@ -116,6 +116,18 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "The total memory (in MB) available to be created for this project", since = "4.2.0") private String memoryAvailable; + @SerializedName("gpulimit") + @Param(description = "the total number of gpus the project can own", since = "4.21.0") + private String gpuLimit; + + @SerializedName("gputotal") + @Param(description = "the total number of gpus owned by project", since = "4.21.0") + private Long gpuTotal; + + @SerializedName("gpuavailable") + @Param(description = "the total number of gpus available to be created for this project", since = "4.21.0") + private String gpuAvailable; + @SerializedName("primarystoragelimit") @Param(description = "The total primary storage space (in GiB) the project can own", since = "4.2.0") private String primaryStorageLimit; @@ -140,6 +152,30 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "The total secondary storage space (in GiB) available to be used for this project", since = "4.2.0") private String secondaryStorageAvailable; + @SerializedName(ApiConstants.BUCKET_LIMIT) + @Param(description = "the total number of buckets which can be stored by this project", since = "4.21.0") + private String bucketLimit; + + @SerializedName(ApiConstants.BUCKET_TOTAL) + @Param(description = "the total number of buckets stored by this project", since = "4.21.0") + private Long bucketTotal; + + @SerializedName(ApiConstants.BUCKET_AVAILABLE) + @Param(description = "the total number of buckets available to this project", since = "4.21.0") + private String bucketAvailable; + + @SerializedName(ApiConstants.OBJECT_STORAGE_LIMIT) + @Param(description = "the total object storage space (in GiB) the project can own", since = "4.21.0") + private String objectStorageLimit; + + @SerializedName(ApiConstants.OBJECT_STORAGE_TOTAL) + @Param(description = "the total object storage space (in GiB) owned by the project", since = "4.21.0") + private Long objectStorageTotal; + + @SerializedName(ApiConstants.OBJECT_STORAGE_AVAILABLE) + @Param(description = "the total object storage space (in GiB) available to the project", since = "4.21.0") + private String objectStorageAvailable; + @SerializedName(ApiConstants.VM_LIMIT) @Param(description = "The total number of Instances that can be deployed by this project", since = "4.2.0") private String vmLimit; @@ -188,6 +224,30 @@ public class ProjectResponse extends BaseResponse implements ResourceLimitAndCou @Param(description = "The total number of Snapshots available for this project", since = "4.2.0") private String snapshotAvailable; + @SerializedName(ApiConstants.BACKUP_LIMIT) + @Param(description = "the total number of backups which can be stored by this project", since = "4.21.0") + private String backupLimit; + + @SerializedName(ApiConstants.BACKUP_TOTAL) + @Param(description = "the total number of backups stored by this project", since = "4.21.0") + private Long backupTotal; + + @SerializedName(ApiConstants.BACKUP_AVAILABLE) + @Param(description = "the total number of backups available to this project", since = "4.21.0") + private String backupAvailable; + + @SerializedName(ApiConstants.BACKUP_STORAGE_LIMIT) + @Param(description = "the total backup storage space (in GiB) the project can own", since = "4.21.0") + private String backupStorageLimit; + + @SerializedName(ApiConstants.BACKUP_STORAGE_TOTAL) + @Param(description = "the total backup storage space (in GiB) owned by the project", since = "4.21.0") + private Long backupStorageTotal; + + @SerializedName(ApiConstants.BACKUP_STORAGE_AVAILABLE) + @Param(description = "the total backup storage space (in GiB) available to the project", since = "4.21.0") + private String backupStorageAvailable; + @SerializedName("templatelimit") @Param(description = "The total number of Templates which can be created by this project", since = "4.2.0") private String templateLimit; @@ -320,6 +380,36 @@ public void setSnapshotAvailable(String snapshotAvailable) { this.snapshotAvailable = snapshotAvailable; } + @Override + public void setBackupLimit(String backupLimit) { + this.backupLimit = backupLimit; + } + + @Override + public void setBackupTotal(Long backupTotal) { + this.backupTotal = backupTotal; + } + + @Override + public void setBackupAvailable(String backupAvailable) { + this.backupAvailable = backupAvailable; + } + + @Override + public void setBackupStorageLimit(String backupStorageLimit) { + this.backupStorageLimit = backupStorageLimit; + } + + @Override + public void setBackupStorageTotal(Long backupStorageTotal) { + this.backupStorageTotal = backupStorageTotal; + } + + @Override + public void setBackupStorageAvailable(String backupStorageAvailable) { + this.backupStorageAvailable = backupStorageAvailable; + } + @Override public void setTemplateLimit(String templateLimit) { this.templateLimit = templateLimit; @@ -405,6 +495,21 @@ public void setMemoryAvailable(String memoryAvailable) { this.memoryAvailable = memoryAvailable; } + @Override + public void setGpuLimit(String gpuLimit) { + this.gpuLimit = gpuLimit; + } + + @Override + public void setGpuTotal(Long gpuTotal) { + this.gpuTotal = gpuTotal; + } + + @Override + public void setGpuAvailable(String gpuAvailable) { + this.gpuAvailable = gpuAvailable; + } + @Override public void setPrimaryStorageLimit(String primaryStorageLimit) { this.primaryStorageLimit = primaryStorageLimit; @@ -435,6 +540,36 @@ public void setSecondaryStorageAvailable(String secondaryStorageAvailable) { this.secondaryStorageAvailable = secondaryStorageAvailable; } + @Override + public void setBucketLimit(String bucketLimit) { + this.bucketLimit = bucketLimit; + } + + @Override + public void setBucketTotal(Long bucketTotal) { + this.bucketTotal = bucketTotal; + } + + @Override + public void setBucketAvailable(String bucketAvailable) { + this.bucketAvailable = bucketAvailable; + } + + @Override + public void setObjectStorageLimit(String objectStorageLimit) { + this.objectStorageLimit = objectStorageLimit; + } + + @Override + public void setObjectStorageTotal(Long objectStorageTotal) { + this.objectStorageTotal = objectStorageTotal; + } + + @Override + public void setObjectStorageAvailable(String objectStorageAvailable) { + this.objectStorageAvailable = objectStorageAvailable; + } + public void setOwners(List> owners) { this.owners = owners; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RegisterUserKeyResponse.java similarity index 96% rename from api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java rename to api/src/main/java/org/apache/cloudstack/api/response/RegisterUserKeyResponse.java index 915fffe51501..4a5fb6b222e6 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RegisterResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RegisterUserKeyResponse.java @@ -23,7 +23,7 @@ import com.cloud.serializer.Param; -public class RegisterResponse extends BaseResponse { +public class RegisterUserKeyResponse extends BaseResponse { @SerializedName(ApiConstants.API_KEY) @Param(description = "The API key of the registered user", isSensitive = true) private String apiKey; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java index f9e6df3a0386..66de71dd7634 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceLimitAndCountResponse.java @@ -48,6 +48,12 @@ public interface ResourceLimitAndCountResponse { public void setMemoryAvailable(String memoryAvailable); + public void setGpuLimit(String gpuLimit); + + public void setGpuTotal(Long gpuTotal); + + public void setGpuAvailable(String gpuAvailable); + public void setPrimaryStorageLimit(String primaryStorageLimit); public void setPrimaryStorageTotal(Long primaryStorageTotal); @@ -84,6 +90,30 @@ public interface ResourceLimitAndCountResponse { public void setSnapshotAvailable(String snapshotAvailable); + public void setBackupLimit(String backupLimit); + + public void setBackupTotal(Long backupTotal); + + public void setBackupAvailable(String backupAvailable); + + public void setBackupStorageLimit(String backupStorageLimit); + + public void setBackupStorageTotal(Long backupStorageTotal); + + public void setBackupStorageAvailable(String backupStorageAvailable); + + void setBucketLimit(String bucketLimit); + + void setBucketTotal(Long bucketTotal); + + void setBucketAvailable(String bucketAvailable); + + void setObjectStorageLimit(String objectStorageLimit); + + void setObjectStorageTotal(Long objectStorageTotal); + + void setObjectStorageAvailable(String objectStorageAvailable); + public void setTemplateLimit(String templateLimit); public void setTemplateTotal(Long templateTotal); diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java new file mode 100644 index 000000000000..ff799b467cff --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/ResourceScheduleResponse.java @@ -0,0 +1,178 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.api.response; + +import java.util.Date; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.BaseResponse; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.schedule.ResourceSchedule; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +@EntityReference(value = ResourceSchedule.class) +public class ResourceScheduleResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "The ID of resource schedule") + private String id; + + @SerializedName(ApiConstants.RESOURCE_TYPE) + @Param(description = "Type of the resource") + private ApiCommandResourceType resourceType; + + @SerializedName(ApiConstants.RESOURCE_ID) + @Param(description = "ID of the resource") + private String resourceId; + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "Description of resource schedule") + private String description; + + @SerializedName(ApiConstants.SCHEDULE) + @Param(description = "Cron formatted resource schedule") + private String schedule; + + @SerializedName(ApiConstants.TIMEZONE) + @Param(description = "Timezone of the schedule") + private String timeZone; + + @SerializedName(ApiConstants.ACTION) + @Param(description = "Action") + private ResourceSchedule.Action action; + + @SerializedName(ApiConstants.ENABLED) + @Param(description = "Resource schedule is enabled") + private boolean enabled; + + @SerializedName(ApiConstants.START_DATE) + @Param(description = "Date from which the schedule is active") + private Date startDate; + + @SerializedName(ApiConstants.END_DATE) + @Param(description = "Date after which the schedule becomes inactive") + private Date endDate; + + @SerializedName(ApiConstants.DETAILS) + @Param(description = "Schedule details") + private Map details; + + @SerializedName(ApiConstants.CREATED) + @Param(description = "Date when the schedule was created") + private Date created; + + public void setId(String id) { + this.id = id; + } + + public String getId() { + return id; + } + + public void setResourceType(ApiCommandResourceType resourceType) { + this.resourceType = resourceType; + } + + public ApiCommandResourceType getResourceType() { + return resourceType; + } + + public void setResourceId(String resourceId) { + this.resourceId = resourceId; + } + + public String getResourceId() { + return resourceId; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } + + public void setSchedule(String schedule) { + this.schedule = schedule; + } + + public String getSchedule() { + return schedule; + } + + public void setTimeZone(String timeZone) { + this.timeZone = timeZone; + } + + public String getTimeZone() { + return timeZone; + } + + public void setAction(ResourceSchedule.Action action) { + this.action = action; + } + + public ResourceSchedule.Action getAction() { + return action; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean getEnabled() { + return enabled; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public Date getStartDate() { + return startDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public Date getEndDate() { + return endDate; + } + + public void setDetails(Map details) { + this.details = details; + } + + public Map getDetails() { + return details; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getCreated() { + return created; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/RouterHealthCheckResultResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/RouterHealthCheckResultResponse.java index 5bdd9b0d2793..9716c564634e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/RouterHealthCheckResultResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/RouterHealthCheckResultResponse.java @@ -19,6 +19,7 @@ import java.util.Date; +import com.cloud.network.VirtualNetworkApplianceService.RouterHealthStatus; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; @@ -34,10 +35,14 @@ public class RouterHealthCheckResultResponse extends BaseResponse { @Param(description = "The type of the health check - basic or advanced") private String checkType; - @SerializedName(ApiConstants.RESULT) - @Param(description = "Result of the health check") + @SerializedName(ApiConstants.SUCCESS) + @Param(description = "The result of the health check if available") private boolean result; + @SerializedName(ApiConstants.STATUS) + @Param(description = "the result of the health check in enum form: {SUCCESS, FAILURE, WARNING, UNKNOWN}") + private RouterHealthStatus state; + @SerializedName(ApiConstants.LAST_UPDATED) @Param(description = "The date this VPC was created") private Date lastUpdated; @@ -54,10 +59,14 @@ public String getCheckType() { return checkType; } - public boolean getResult() { + public Boolean getResult() { return result; } + public RouterHealthStatus getState() { + return state; + } + public Date getLastUpdated() { return lastUpdated; } @@ -74,10 +83,14 @@ public void setCheckType(String checkType) { this.checkType = checkType; } - public void setResult(boolean result) { + public void setResult(Boolean result) { this.result = result; } + public void setState(RouterHealthStatus state) { + this.state = state; + } + public void setLastUpdated(Date lastUpdated) { this.lastUpdated = lastUpdated; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java index 224893072ba0..99c50829f048 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ServiceOfferingResponse.java @@ -80,7 +80,7 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "True if the Instance needs to be volatile, i.e., on every reboot of Instance from API root disk is discarded and creates a new root disk") private Boolean isVolatile; - @SerializedName("storagetags") + @SerializedName(ApiConstants.STORAGE_TAGS) @Param(description = "The tags for the service offering") private String tags; @@ -197,7 +197,7 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { private Boolean isCustomized; @SerializedName("cacheMode") - @Param(description = "The cache mode to use for this disk offering. none, writeback or writethrough", since = "4.14") + @Param(description = "The cache mode to use for this disk offering. none, writeback, writethrough or hypervisor default", since = "4.14") private String cacheMode; @SerializedName("vspherestoragepolicy") @@ -234,10 +234,58 @@ public class ServiceOfferingResponse extends BaseResponseWithAnnotations { @Param(description = "True if Instance root disk will be encrypted on storage", since = "4.18") private Boolean encryptRoot; + @SerializedName(ApiConstants.GPU_CARD_ID) + @Param(description = "the ID of the gpu card to which service offering is linked", since = "4.21") + private String gpuCardId; + + @SerializedName(ApiConstants.GPU_CARD_NAME) + @Param(description = "the name of the gpu card to which service offering is linked", since = "4.21") + private String gpuCardName; + + @SerializedName(ApiConstants.VGPU_PROFILE_ID) + @Param(description = "the ID of the vgpu profile to which service offering is linked", since = "4.21") + private String vgpuProfileId; + + @SerializedName(ApiConstants.VGPU_PROFILE_NAME) + @Param(description = "the name of the vgpu profile to which service offering is linked", since = "4.21") + private String vgpuProfileName; + + @SerializedName(ApiConstants.VIDEORAM) + @Param(description = "the video RAM size in MB") + private Long videoRam; + + @SerializedName(ApiConstants.MAXHEADS) + @Param(description = "the maximum number of display heads") + private Long maxHeads; + + @SerializedName(ApiConstants.MAXRESOLUTIONX) + @Param(description = "the maximum X resolution") + private Long maxResolutionX; + + @SerializedName(ApiConstants.MAXRESOLUTIONY) + @Param(description = "the maximum Y resolution") + private Long maxResolutionY; + + @SerializedName(ApiConstants.GPU_COUNT) + @Param(description = "the count of GPUs to attach ", since = "4.21") + private Integer gpuCount; + + @SerializedName(ApiConstants.GPU_DISPLAY) + @Param(description = "whether GPU device is used for display or not ", since = "4.21") + private Boolean gpuDisplay; + @SerializedName(ApiConstants.PURGE_RESOURCES) @Param(description = "Whether to cleanup VM and its associated resource upon expunge", since = "4.20") private Boolean purgeResources; + @SerializedName(ApiConstants.INSTANCE_LEASE_DURATION) + @Param(description = "Instance lease duration (in days) for service offering", since = "4.21.0") + private Integer leaseDuration; + + @SerializedName(ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION) + @Param(description = "Action to be taken once lease is over", since = "4.21.0") + private String leaseExpiryAction; + public ServiceOfferingResponse() { } @@ -505,6 +553,22 @@ public void setCacheMode(String cacheMode) { this.cacheMode = cacheMode; } + public Integer getLeaseDuration() { + return leaseDuration; + } + + public void setLeaseDuration(Integer leaseDuration) { + this.leaseDuration = leaseDuration; + } + + public String getLeaseExpiryAction() { + return leaseExpiryAction; + } + + public void setLeaseExpiryAction(String leaseExpiryAction) { + this.leaseExpiryAction = leaseExpiryAction; + } + public String getVsphereStoragePolicy() { return vsphereStoragePolicy; } @@ -560,6 +624,86 @@ public String getDiskOfferingDisplayText() { public void setEncryptRoot(Boolean encrypt) { this.encryptRoot = encrypt; } + public String getVgpuProfileName() { + return vgpuProfileName; + } + + public void setVgpuProfileName(String vgpuProfileName) { + this.vgpuProfileName = vgpuProfileName; + } + + public Long getVideoRam() { + return videoRam; + } + + public void setVideoRam(Long videoRam) { + this.videoRam = videoRam; + } + + public Long getMaxHeads() { + return maxHeads; + } + + public void setMaxHeads(Long maxHeads) { + this.maxHeads = maxHeads; + } + + public Long getMaxResolutionX() { + return maxResolutionX; + } + + public void setMaxResolutionX(Long maxResolutionX) { + this.maxResolutionX = maxResolutionX; + } + + public Long getMaxResolutionY() { + return maxResolutionY; + } + + public void setMaxResolutionY(Long maxResolutionY) { + this.maxResolutionY = maxResolutionY; + } + + public String getVgpuProfileId() { + return vgpuProfileId; + } + + public void setVgpuProfileId(String vgpuProfileId) { + this.vgpuProfileId = vgpuProfileId; + } + + public String getGpuCardName() { + return gpuCardName; + } + + public void setGpuCardName(String gpuCardName) { + this.gpuCardName = gpuCardName; + } + + public String getGpuCardId() { + return gpuCardId; + } + + public void setGpuCardId(String gpuCardId) { + this.gpuCardId = gpuCardId; + } + + public Integer getGpuCount() { + return gpuCount; + } + + public void setGpuCount(Integer gpuCount) { + this.gpuCount = gpuCount; + } + + public Boolean getGpuDisplay() { + return gpuDisplay; + } + + public void setGpuDisplay(Boolean gpuDisplay) { + this.gpuDisplay = gpuDisplay; + } + public void setPurgeResources(Boolean purgeResources) { this.purgeResources = purgeResources; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java index 4e5820279a2e..b121ef7ce61e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/Site2SiteCustomerGatewayResponse.java @@ -114,6 +114,14 @@ public class Site2SiteCustomerGatewayResponse extends BaseResponseWithAnnotation @Param(description = "Which IKE Version to use, one of ike (autoselect), IKEv1, or IKEv2. Defaults to ike") private String ikeVersion; + @SerializedName(ApiConstants.OBSOLETE_PARAMETERS) + @Param(description = "Contains the list of obsolete/insecure cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0") + private String obsoleteParameters; + + @SerializedName(ApiConstants.EXCLUDED_PARAMETERS) + @Param(description = "Contains the list of excluded/not allowed cryptographic parameters that the vpn customer gateway is using.", since = "4.23.0") + private String excludedParameters; + public void setId(String id) { this.id = id; } @@ -202,4 +210,12 @@ public void setDomainPath(String domainPath) { this.domainPath = domainPath; } + public void setContainsObsoleteParameters(String obsoleteParameters) { + this.obsoleteParameters = obsoleteParameters; + } + + public void setContainsExcludedParameters(String excludedParameters) { + this.excludedParameters = excludedParameters; + } + } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java index 05d0ffdb731a..3a40f3dfa982 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotPolicyResponse.java @@ -16,17 +16,16 @@ // under the License. package org.apache.cloudstack.api.response; -import java.util.LinkedHashSet; -import java.util.Set; - +import com.cloud.serializer.Param; +import com.cloud.storage.snapshot.SnapshotPolicy; +import com.google.gson.annotations.SerializedName; import org.apache.cloudstack.acl.RoleType; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponseWithTagInformation; import org.apache.cloudstack.api.EntityReference; -import com.cloud.serializer.Param; -import com.cloud.storage.snapshot.SnapshotPolicy; -import com.google.gson.annotations.SerializedName; +import java.util.LinkedHashSet; +import java.util.Set; @EntityReference(value = SnapshotPolicy.class) public class SnapshotPolicyResponse extends BaseResponseWithTagInformation { @@ -38,6 +37,10 @@ public class SnapshotPolicyResponse extends BaseResponseWithTagInformation { @Param(description = "The ID of the disk volume") private String volumeId; + @SerializedName("volumename") + @Param(description = "the name of the disk volume") + private String volumeName; + @SerializedName("schedule") @Param(description = "Time the Snapshot is scheduled to be taken.") private String schedule; @@ -62,9 +65,14 @@ public class SnapshotPolicyResponse extends BaseResponseWithTagInformation { @Param(description = "The list of zones in which snapshot backup is scheduled", responseObject = ZoneResponse.class, since = "4.19.0") protected Set zones; + @SerializedName(ApiConstants.STORAGE) + @Param(description = "The list of pools in which snapshot backup is scheduled", responseObject = StoragePoolResponse.class, since = "4.21.0") + protected Set storagePools; + public SnapshotPolicyResponse() { tags = new LinkedHashSet(); zones = new LinkedHashSet<>(); + storagePools = new LinkedHashSet<>(); } public String getId() { @@ -83,6 +91,10 @@ public void setVolumeId(String volumeId) { this.volumeId = volumeId; } + public void setVolumeName(String volumeName) { + this.volumeName = volumeName; + } + public String getSchedule() { return schedule; } @@ -130,4 +142,6 @@ public void setTags(Set tags) { public void setZones(Set zones) { this.zones = zones; } + + public void setStoragePools(Set pools) { this.storagePools = pools; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java index 7a4090eb7259..3db6fd87ed59 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SnapshotResponse.java @@ -107,6 +107,10 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements @Param(description = "Physical size of backed up Snapshot on image store") private long physicalSize; + @SerializedName(ApiConstants.CHAIN_SIZE) + @Param(description = "chain size of snapshot including all parent snapshots. Shown only for incremental snapshots if snapshot.show.chain.size setting is set to true", since = "4.21.0") + private Long chainSize; + @SerializedName(ApiConstants.ZONE_ID) @Param(description = "ID of the availability zone") private String zoneId; @@ -151,6 +155,14 @@ public class SnapshotResponse extends BaseResponseWithTagInformation implements @Param(description = "download progress of a snapshot", since = "4.19.0") private Map downloadDetails; + @SerializedName("parent") + @Param(description = "The parent ID of the Snapshot", since = "4.22.1") + private String parent; + + @SerializedName("parentname") + @Param(description = "The parent name of the Snapshot", since = "4.22.1") + private String parentName; + public SnapshotResponse() { tags = new LinkedHashSet(); } @@ -244,6 +256,10 @@ public void setPhysicalSize(long physicalSize) { this.physicalSize = physicalSize; } + public void setChainSize(long chainSize) { + this.chainSize = chainSize; + } + @Override public void setProjectId(String projectId) { this.projectId = projectId; @@ -305,4 +321,12 @@ public void setDatastoreType(String datastoreType) { public void setDownloadDetails(Map downloadDetails) { this.downloadDetails = downloadDetails; } + + public void setParent(String parent) { + this.parent = parent; + } + + public void setParentName(String parentName) { + this.parentName = parentName; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SslCertResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SslCertResponse.java index ff2f9562027e..d4add82f666c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SslCertResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SslCertResponse.java @@ -27,8 +27,6 @@ import org.apache.cloudstack.network.tls.SslCert; import com.cloud.serializer.Param; -//import org.apache.cloudstack.api.EntityReference; - @EntityReference(value = SslCert.class) public class SslCertResponse extends BaseResponse { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java index 395c2cc967bc..ed0e050b9652 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StaticRouteResponse.java @@ -42,9 +42,17 @@ public class StaticRouteResponse extends BaseResponse implements ControlledEntit @Param(description = "VPC the static route belongs to") private String vpcId; - @SerializedName(ApiConstants.GATEWAY_ID) + @SerializedName(ApiConstants.VPC_GATEWAY_ID) @Param(description = "VPC gateway the route is created for") - private String gatewayId; + private String vpcGatewayId; + + @SerializedName(ApiConstants.VPC_GATEWAY_IP) + @Param(description = "IP of VPC gateway the route is created for", since = "4.21.0") + private String vpcGatewayIp; + + @SerializedName(ApiConstants.NEXT_HOP) + @Param(description = "Next hop of the static route", since = "4.21.0") + private String nextHop; @SerializedName(ApiConstants.CIDR) @Param(description = "Static route CIDR") @@ -95,8 +103,16 @@ public void setVpcId(String vpcId) { this.vpcId = vpcId; } - public void setGatewayId(String gatewayId) { - this.gatewayId = gatewayId; + public void setVpcGatewayId(String vpcGatewayId) { + this.vpcGatewayId = vpcGatewayId; + } + + public void setVpcGatewayIp(String vpcGatewayIp) { + this.vpcGatewayIp = vpcGatewayIp; + } + + public void setNextHop(String nextHop) { + this.nextHop = nextHop; } public void setCidr(String cidr) { diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StorageAccessGroupResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StorageAccessGroupResponse.java new file mode 100644 index 000000000000..a6324dd62a9f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/StorageAccessGroupResponse.java @@ -0,0 +1,108 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; +import com.cloud.serializer.Param; + +public class StorageAccessGroupResponse extends BaseResponse { + @SerializedName(ApiConstants.ID) + @Param(description = "the ID of the storage access group") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "the name of the storage access group") + private String name; + + @SerializedName("hosts") + @Param(description = "List of Hosts in the Storage Access Group") + private ListResponse hostResponseList; + + @SerializedName("clusters") + @Param(description = "List of Clusters in the Storage Access Group") + private ListResponse clusterResponseList; + + @SerializedName("pods") + @Param(description = "List of Pods in the Storage Access Group") + private ListResponse podResponseList; + + @SerializedName("zones") + @Param(description = "List of Zones in the Storage Access Group") + private ListResponse zoneResponseList; + + @SerializedName("storagepools") + @Param(description = "List of Storage Pools in the Storage Access Group") + private ListResponse storagePoolResponseList; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public ListResponse getHostResponseList() { + return hostResponseList; + } + + public void setHostResponseList(ListResponse hostResponseList) { + this.hostResponseList = hostResponseList; + } + + public ListResponse getClusterResponseList() { + return clusterResponseList; + } + + public void setClusterResponseList(ListResponse clusterResponseList) { + this.clusterResponseList = clusterResponseList; + } + + public ListResponse getPodResponseList() { + return podResponseList; + } + + public void setPodResponseList(ListResponse podResponseList) { + this.podResponseList = podResponseList; + } + + public ListResponse getZoneResponseList() { + return zoneResponseList; + } + + public void setZoneResponseList(ListResponse zoneResponseList) { + this.zoneResponseList = zoneResponseList; + } + + public ListResponse getStoragePoolResponseList() { + return storagePoolResponseList; + } + + public void setStoragePoolResponseList(ListResponse storagePoolResponseList) { + this.storagePoolResponseList = storagePoolResponseList; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java index d34feed9d9cb..25e67115ab4a 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/StoragePoolResponse.java @@ -77,19 +77,24 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { @Param(description = "The name of the cluster for the storage pool") private String clusterName; + @SerializedName(ApiConstants.CAPACITY_BYTES) + @Param(description = "bytes CloudStack can provision from this storage pool", since = "4.22.0") + private Long capacityBytes; + + @Deprecated(since = "4.22.0") @SerializedName("disksizetotal") @Param(description = "The total disk size of the storage pool") private Long diskSizeTotal; @SerializedName("disksizeallocated") - @Param(description = "The host's currently allocated disk size") + @Param(description = "The pool's currently allocated disk size") private Long diskSizeAllocated; @SerializedName("disksizeused") - @Param(description = "The host's currently used disk size") + @Param(description = "The pool's currently used disk size") private Long diskSizeUsed; - @SerializedName("capacityiops") + @SerializedName(ApiConstants.CAPACITY_IOPS) @Param(description = "IOPS CloudStack can provision from this storage pool") private Long capacityIops; @@ -109,6 +114,10 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { @Param(description = "The tags for the storage pool") private String tags; + @SerializedName(ApiConstants.STORAGE_ACCESS_GROUPS) + @Param(description = "the storage access groups for the storage pool", since = "4.21.0") + private String storageAccessGroups; + @SerializedName(ApiConstants.NFS_MOUNT_OPTIONS) @Param(description = "the nfs mount options for the storage pool", since = "4.19.1") private String nfsMountOpts; @@ -149,6 +158,10 @@ public class StoragePoolResponse extends BaseResponseWithAnnotations { @Param(description = "whether this pool is managed or not") private Boolean managed; + @SerializedName(ApiConstants.DETAILS) + @Param(description = "the storage pool details") + private Map details; + public Map getCaps() { return caps; } @@ -280,6 +293,14 @@ public void setClusterName(String clusterName) { this.clusterName = clusterName; } + public Long getCapacityBytes() { + return capacityBytes; + } + + public void setCapacityBytes(Long capacityBytes) { + this.capacityBytes = capacityBytes; + } + public Long getDiskSizeTotal() { return diskSizeTotal; } @@ -340,6 +361,14 @@ public void setTags(String tags) { this.tags = tags; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + public Boolean getIsTagARule() { return isTagARule; } @@ -407,4 +436,12 @@ public Boolean getManaged() { public void setManaged(Boolean managed) { this.managed = managed; } + + public Map getDetails() { + return details; + } + + public void setDetails(Map details) { + this.details = details; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java index 316852552dca..a3ed88c27356 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/SystemVmResponse.java @@ -38,14 +38,6 @@ public class SystemVmResponse extends BaseResponseWithAnnotations { @Param(description = "The System VM type") private String systemVmType; - @SerializedName("jobid") - @Param(description = "The job ID associated with the System VM. This is only displayed if the router listed is part of a currently running asynchronous job.") - private String jobId; - - @SerializedName("jobstatus") - @Param(description = "The job status associated with the System VM. This is only displayed if the router listed is part of a currently running asynchronous job.") - private Integer jobStatus; - @SerializedName("zoneid") @Param(description = "The Zone ID for the System VM") private String zoneId; @@ -134,6 +126,10 @@ public class SystemVmResponse extends BaseResponseWithAnnotations { @Param(description = "The public netmask for the System VM") private String publicNetmask; + @SerializedName("storageip") + @Param(description = "the ip address for the system VM on the storage network") + private String storageIp; + @SerializedName("templateid") @Param(description = "The Template ID for the System VM") private String templateId; @@ -363,6 +359,14 @@ public void setPublicNetmask(String publicNetmask) { this.publicNetmask = publicNetmask; } + public String getStorageIp() { + return storageIp; + } + + public void setStorageIp(String storageIp) { + this.storageIp = storageIp; + } + public String getTemplateId() { return templateId; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java index 2250d03a6c62..2e706586f01d 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TemplateResponse.java @@ -93,6 +93,8 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements @Param(description = "The name of the OS type for this Template.") private String osTypeName; + private transient Long osTypeCategoryId; + @SerializedName(ApiConstants.ACCOUNT_ID) @Param(description = "The Account id to which the Template belongs") private String accountId; @@ -208,6 +210,11 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements since = "4.15") private Boolean deployAsIs; + @SerializedName(ApiConstants.FOR_CKS) + @Param(description = "If true it indicates that the template can be used for CKS cluster deployments", + since = "4.21.0") + private Boolean forCks; + @SerializedName(ApiConstants.DEPLOY_AS_IS_DETAILS) @Param(description = "VMware only: additional key/value details tied with deploy-as-is Template", since = "4.15") @@ -247,6 +254,12 @@ public class TemplateResponse extends BaseResponseWithTagInformation implements @SerializedName(ApiConstants.USER_DATA_PARAMS) @Param(description = "List of parameters which contains the list of keys or string parameters that are needed to be passed for any variables declared in userdata", since = "4.18.0") private String userDataParams; + @SerializedName(ApiConstants.EXTENSION_ID) @Param(description="The ID of extension linked to this template", since = "4.21.0") + private String extensionId; + + @SerializedName(ApiConstants.EXTENSION_NAME) @Param(description="The name of extension linked to this template", since = "4.21.0") + private String extensionName; + public TemplateResponse() { tags = new LinkedHashSet<>(); } @@ -285,6 +298,14 @@ public void setOsTypeName(String osTypeName) { this.osTypeName = osTypeName; } + public Long getOsTypeCategoryId() { + return osTypeCategoryId; + } + + public void setOsTypeCategoryId(Long osTypeCategoryId) { + this.osTypeCategoryId = osTypeCategoryId; + } + public void setId(String id) { this.id = id; } @@ -453,6 +474,10 @@ public void setDeployAsIs(Boolean deployAsIs) { this.deployAsIs = deployAsIs; } + public void setForCks(Boolean forCks) { + this.forCks = forCks; + } + public void setParentTemplateId(String parentTemplateId) { this.parentTemplateId = parentTemplateId; } @@ -528,4 +553,20 @@ public void setUserDataParams(String userDataParams) { public void setArch(String arch) { this.arch = arch; } + + public String getExtensionId() { + return extensionId; + } + + public void setExtensionId(String extensionId) { + this.extensionId = extensionId; + } + + public String getExtensionName() { + return extensionName; + } + + public void setExtensionName(String extensionName) { + this.extensionName = extensionName; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java index bbf7159a4854..2b8af97f160e 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/TrafficTypeResponse.java @@ -56,6 +56,14 @@ public class TrafficTypeResponse extends BaseResponse { @Param(description = "The Network name label of the physical device dedicated to this traffic on a HyperV host") private String hypervNetworkLabel; + @SerializedName(ApiConstants.VLAN) + @Param(description = "The VLAN id to be used for Management traffic by VMware host") + private String vlan; + + @SerializedName(ApiConstants.ISOLATION_METHODS) + @Param(description = "isolation methods for the physical network traffic") + private String isolationMethods; + @SerializedName(ApiConstants.OVM3_NETWORK_LABEL) @Param(description = "The Network name of the physical device dedicated to this traffic on an OVM3 host") private String ovm3NetworkLabel; @@ -128,4 +136,20 @@ public String getOvm3Label() { public void setOvm3Label(String ovm3Label) { this.ovm3NetworkLabel = ovm3Label; } + + public String getIsolationMethods() { + return isolationMethods; + } + + public void setIsolationMethods(String isolationMethods) { + this.isolationMethods = isolationMethods; + } + + public String getVlan() { + return vlan; + } + + public void setVlan(String vlan) { + this.vlan = vlan; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UnmanageVMInstanceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UnmanageVMInstanceResponse.java index 1da5ab89a606..5b3f15b0ddce 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UnmanageVMInstanceResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UnmanageVMInstanceResponse.java @@ -24,7 +24,7 @@ public class UnmanageVMInstanceResponse extends BaseResponse { - @SerializedName(ApiConstants.RESULT) + @SerializedName(ApiConstants.SUCCESS) @Param(description = "Result of the unmanage Instance operation") private boolean success; @@ -32,6 +32,10 @@ public class UnmanageVMInstanceResponse extends BaseResponse { @Param(description = "Details of the unmanage Instance operation") private String details; + @SerializedName(ApiConstants.HOST_ID) + @Param(description = "The ID of the host used for unmanaged Instance") + private String hostId; + public UnmanageVMInstanceResponse() { } @@ -55,4 +59,12 @@ public String getDetails() { public void setDetails(String details) { this.details = details; } + + public String getHostId() { + return hostId; + } + + public void setHostId(String hostId) { + this.hostId = hostId; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java index 195323b741d2..3f2dc045e87c 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UnmanagedInstanceResponse.java @@ -51,6 +51,14 @@ public class UnmanagedInstanceResponse extends BaseResponse { @Param(description = "The name of the host to which Instance belongs") private String hostName; + @SerializedName(ApiConstants.HYPERVISOR) + @Param(description = "The hypervisor to which Instance belongs") + private String hypervisor; + + @SerializedName(ApiConstants.HYPERVISOR_VERSION) + @Param(description = "The hypervisor version of the host to which Instance belongs") + private String hypervisorVersion; + @SerializedName(ApiConstants.POWER_STATE) @Param(description = "The power state of the Instance") private String powerState; @@ -140,6 +148,22 @@ public void setHostName(String hostName) { this.hostName = hostName; } + public String getHypervisor() { + return hypervisor; + } + + public void setHypervisor(String hypervisor) { + this.hypervisor = hypervisor; + } + + public String getHypervisorVersion() { + return hypervisorVersion; + } + + public void setHypervisorVersion(String hypervisorVersion) { + this.hypervisorVersion = hypervisorVersion; + } + public String getPowerState() { return powerState; } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UpgradeRouterTemplateResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UpgradeRouterTemplateResponse.java index 3174fa48067b..dbe990c6dbd3 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UpgradeRouterTemplateResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UpgradeRouterTemplateResponse.java @@ -16,27 +16,10 @@ // under the License. package org.apache.cloudstack.api.response; -import com.google.gson.annotations.SerializedName; - -import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; import org.apache.cloudstack.jobs.JobInfo; -import com.cloud.serializer.Param; - @EntityReference(value = JobInfo.class) -@SuppressWarnings("unused") public class UpgradeRouterTemplateResponse extends BaseResponse { - @SerializedName(ApiConstants.JOB_ID) - @Param(description = "The ID of AsyncJob") - private String asyncJobId; - - public String getAsyncJobId() { - return asyncJobId; - } - - public void setAsyncJobId(String asyncJobId) { - this.asyncJobId = asyncJobId; - } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java index 9566e10991d1..61b025b206eb 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserResponse.java @@ -67,7 +67,7 @@ public class UserResponse extends BaseResponse implements SetResourceIconRespons @Param(description = "The Account type of the user") private Integer accountType; - @SerializedName("usersource") + @SerializedName(ApiConstants.USER_SOURCE) @Param(description = "The source type of the user in lowercase, such as native, ldap, saml2") private String userSource; @@ -95,12 +95,12 @@ public class UserResponse extends BaseResponse implements SetResourceIconRespons @Param(description = "The timezone user was created in") private String timezone; - @SerializedName("apikey") + @SerializedName(ApiConstants.API_KEY) @Param(description = "The API key of the user", isSensitive = true) private String apiKey; @Deprecated - @SerializedName("secretkey") + @SerializedName(ApiConstants.SECRET_KEY) @Param(description = "The secret key of the user", isSensitive = true) private String secretKey; diff --git a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java index fb7c4389928d..e08c6019f150 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/UserVmResponse.java @@ -41,7 +41,7 @@ @SuppressWarnings("unused") @EntityReference(value = {VirtualMachine.class, UserVm.class, VirtualRouter.class}) -public class UserVmResponse extends BaseResponseWithTagInformation implements ControlledEntityResponse, SetResourceIconResponse { +public class UserVmResponse extends BaseResponseWithTagInformation implements ControlledViewEntityResponse, SetResourceIconResponse { @SerializedName(ApiConstants.ID) @Param(description = "The ID of the Instance") private String id; @@ -166,6 +166,14 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "An alternate display text of the ISO attached to the Instance") private String isoDisplayText; + @SerializedName("isos") + @Param(description = "All ISOs attached to the Instance, keyed by cdrom slot. The first entry mirrors isoid/isoname for back-compat.", responseObject = AttachedIsoResponse.class, since = "4.23.0") + private List isos; + + @SerializedName("isomaxcount") + @Param(description = "Maximum number of ISOs that may be attached to this Instance, after applying the cluster-scoped vm.iso.max.count and the hypervisor's own cap.", since = "4.23.0") + private Integer isoMaxCount; + @SerializedName(ApiConstants.SERVICE_OFFERING_ID) @Param(description = "The ID of the service offering of the Instance") private String serviceOfferingId; @@ -182,6 +190,42 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "The name of the disk offering of the Instance. This parameter should not be used for retrieving disk offering details of DATA volumes. Use listVolumes API instead", since = "4.4") private String diskOfferingName; + @SerializedName(ApiConstants.GPU_CARD_ID) + @Param(description = "the ID of the gpu card to which service offering is linked", since = "4.21") + private String gpuCardId; + + @SerializedName(ApiConstants.GPU_CARD_NAME) + @Param(description = "the name of the gpu card to which service offering is linked", since = "4.21") + private String gpuCardName; + + @SerializedName(ApiConstants.VGPU_PROFILE_ID) + @Param(description = "the ID of the vgpu profile to which service offering is linked", since = "4.21") + private String vgpuProfileId; + + @SerializedName(ApiConstants.VGPU_PROFILE_NAME) + @Param(description = "the name of the vgpu profile to which service offering is linked", since = "4.21") + private String vgpuProfileName; + + @SerializedName(ApiConstants.VIDEORAM) + @Param(description = "the video RAM size in MB") + private Long videoRam; + + @SerializedName(ApiConstants.MAXHEADS) + @Param(description = "the maximum number of display heads") + private Long maxHeads; + + @SerializedName(ApiConstants.MAXRESOLUTIONX) + @Param(description = "the maximum X resolution") + private Long maxResolutionX; + + @SerializedName(ApiConstants.MAXRESOLUTIONY) + @Param(description = "the maximum Y resolution") + private Long maxResolutionY; + + @SerializedName(ApiConstants.GPU_COUNT) + @Param(description = "the count of GPUs on the virtual machine", since = "4.21") + private Integer gpuCount; + @SerializedName(ApiConstants.BACKUP_OFFERING_ID) @Param(description = "The ID of the backup offering of the Instance", since = "4.14") private String backupOfferingId; @@ -190,6 +234,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "The name of the backup offering of the Instance", since = "4.14") private String backupOfferingName; + @SerializedName(ApiConstants.BACKUP_PROVIDER) + @Param(description = "The name of the backup provider of the offering attached to the Instance", since = "4.23.0") + private String backupProvider; + @SerializedName("forvirtualnetwork") @Param(description = "The virtual Network for the service offering") private Boolean forVirtualNetwork; @@ -304,6 +352,10 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "List of read-only Instance details as comma separated string.", since = "4.16.0") private String readOnlyDetails; + @SerializedName("alloweddetails") + @Param(description = "List of allowed Vm details as comma separated string if VM instance settings are read from OVA.", since = "4.22.1") + private String allowedDetails; + @SerializedName(ApiConstants.SSH_KEYPAIRS) @Param(description = "SSH key-pairs") private String keyPairNames; @@ -392,7 +444,7 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "VNF details", since = "4.19.0") private Map vnfDetails; - @SerializedName((ApiConstants.VM_TYPE)) + @SerializedName(ApiConstants.VM_TYPE) @Param(description = "User VM type", since = "4.20.0") private String vmType; @@ -400,6 +452,18 @@ public class UserVmResponse extends BaseResponseWithTagInformation implements Co @Param(description = "CPU arch of the VM", since = "4.20.1") private String arch; + @SerializedName(ApiConstants.INSTANCE_LEASE_DURATION) + @Param(description = "Instance lease duration in days", since = "4.21.0") + private Integer leaseDuration; + + @SerializedName(ApiConstants.INSTANCE_LEASE_EXPIRY_DATE) + @Param(description = "Instance lease expiry date", since = "4.21.0") + private Date leaseExpiryDate; + + @SerializedName(ApiConstants.INSTANCE_LEASE_EXPIRY_ACTION) + @Param(description = "Instance lease expiry action", since = "4.21.0") + private String leaseExpiryAction; + public UserVmResponse() { securityGroupList = new LinkedHashSet<>(); nics = new TreeSet<>(Comparator.comparingInt(x -> Integer.parseInt(x.getDeviceId()))); @@ -553,6 +617,42 @@ public String getDiskOfferingName() { return diskOfferingName; } + public String getGpuCardId() { + return gpuCardId; + } + + public String getGpuCardName() { + return gpuCardName; + } + + public String getVgpuProfileId() { + return vgpuProfileId; + } + + public String getVgpuProfileName() { + return vgpuProfileName; + } + + public Long getVideoRam() { + return videoRam; + } + + public Long getMaxHeads() { + return maxHeads; + } + + public Long getMaxResolutionX() { + return maxResolutionX; + } + + public Long getMaxResolutionY() { + return maxResolutionY; + } + + public Integer getGpuCount() { + return gpuCount; + } + public String getBackupOfferingId() { return backupOfferingId; } @@ -783,6 +883,22 @@ public void setIsoId(String isoId) { this.isoId = isoId; } + public void setIsos(List isos) { + this.isos = isos; + } + + public List getIsos() { + return isos; + } + + public void setIsoMaxCount(Integer isoMaxCount) { + this.isoMaxCount = isoMaxCount; + } + + public Integer getIsoMaxCount() { + return isoMaxCount; + } + public void setIsoName(String isoName) { this.isoName = isoName; } @@ -835,6 +951,42 @@ public void setDiskOfferingName(String diskOfferingName) { this.diskOfferingName = diskOfferingName; } + public void setGpuCardId(String gpuCardId) { + this.gpuCardId = gpuCardId; + } + + public void setGpuCardName(String gpuCardName) { + this.gpuCardName = gpuCardName; + } + + public void setVgpuProfileId(String vgpuProfileId) { + this.vgpuProfileId = vgpuProfileId; + } + + public void setVgpuProfileName(String vgpuProfileName) { + this.vgpuProfileName = vgpuProfileName; + } + + public void setVideoRam(Long videoRam) { + this.videoRam = videoRam; + } + + public void setMaxHeads(Long maxHeads) { + this.maxHeads = maxHeads; + } + + public void setMaxResolutionX(Long maxResolutionX) { + this.maxResolutionX = maxResolutionX; + } + + public void setMaxResolutionY(Long maxResolutionY) { + this.maxResolutionY = maxResolutionY; + } + + public void setGpuCount(Integer gpuCount) { + this.gpuCount = gpuCount; + } + public void setBackupOfferingId(String backupOfferingId) { this.backupOfferingId = backupOfferingId; } @@ -971,6 +1123,10 @@ public void setReadOnlyDetails(String readOnlyDetails) { this.readOnlyDetails = readOnlyDetails; } + public void setAllowedDetails(String allowedDetails) { + this.allowedDetails = allowedDetails; + } + public void setOsTypeId(String osTypeId) { this.osTypeId = osTypeId; } @@ -995,6 +1151,10 @@ public String getReadOnlyDetails() { return readOnlyDetails; } + public String getAllowedDetails() { + return allowedDetails; + } + public Boolean getDynamicallyScalable() { return isDynamicallyScalable; } @@ -1181,4 +1341,36 @@ public String getArch() { public void setArch(String arch) { this.arch = arch; } + + public Integer getLeaseDuration() { + return leaseDuration; + } + + public void setLeaseDuration(Integer leaseDuration) { + this.leaseDuration = leaseDuration; + } + + public String getLeaseExpiryAction() { + return leaseExpiryAction; + } + + public void setLeaseExpiryAction(String leaseExpiryAction) { + this.leaseExpiryAction = leaseExpiryAction; + } + + public Date getLeaseExpiryDate() { + return leaseExpiryDate; + } + + public void setLeaseExpiryDate(Date leaseExpiryDate) { + this.leaseExpiryDate = leaseExpiryDate; + } + + public String getBackupProvider() { + return backupProvider; + } + + public void setBackupProvider(String backupProvider) { + this.backupProvider = backupProvider; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java index 6800c25b0234..34455b3a0bf4 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VMScheduleResponse.java @@ -23,11 +23,11 @@ import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.BaseResponse; import org.apache.cloudstack.api.EntityReference; -import org.apache.cloudstack.vm.schedule.VMSchedule; +import org.apache.cloudstack.schedule.ResourceSchedule; import java.util.Date; -@EntityReference(value = VMSchedule.class) +@EntityReference(value = ResourceSchedule.class) public class VMScheduleResponse extends BaseResponse { @SerializedName(ApiConstants.ID) @Param(description = "The ID of Instance schedule") @@ -51,7 +51,7 @@ public class VMScheduleResponse extends BaseResponse { @SerializedName(ApiConstants.ACTION) @Param(description = "Action") - private VMSchedule.Action action; + private ResourceSchedule.Action action; @SerializedName(ApiConstants.ENABLED) @Param(description = "VM schedule is enabled") @@ -69,6 +69,20 @@ public class VMScheduleResponse extends BaseResponse { @Param(description = "Date when the schedule was created") private Date created; + public VMScheduleResponse(ResourceScheduleResponse scheduleResponse) { + super("vmschedule"); + this.id = scheduleResponse.getId(); + this.vmId = scheduleResponse.getResourceId(); + this.description = scheduleResponse.getDescription(); + this.schedule = scheduleResponse.getSchedule(); + this.timeZone = scheduleResponse.getTimeZone(); + this.action = scheduleResponse.getAction(); + this.enabled = scheduleResponse.getEnabled(); + this.startDate = scheduleResponse.getStartDate(); + this.endDate = scheduleResponse.getEndDate(); + this.created = scheduleResponse.getCreated(); + } + public void setId(String id) { this.id = id; } @@ -89,7 +103,7 @@ public void setTimeZone(String timeZone) { this.timeZone = timeZone; } - public void setAction(VMSchedule.Action action) { + public void setAction(ResourceSchedule.Action action) { this.action = action; } @@ -105,5 +119,7 @@ public void setEndDate(Date endDate) { this.endDate = endDate; } - public void setCreated(Date created) {this.created = created;} + public void setCreated(Date created) { + this.created = created; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java new file mode 100644 index 000000000000..382b391ef592 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/api/response/VgpuProfileResponse.java @@ -0,0 +1,135 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.EntityReference; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.gpu.VgpuProfile; + +@EntityReference(value = VgpuProfile.class) +public class VgpuProfileResponse extends GpuCardResponse { + + @SerializedName(ApiConstants.DESCRIPTION) + @Param(description = "the description of the vGPU profile") + private String description; + + @SerializedName(ApiConstants.GPU_CARD_ID) + @Param(description = "the ID of the GPU card associated with this vGPU profile") + private String gpuCardId; + + @SerializedName(ApiConstants.GPU_CARD_NAME) + @Param(description = "the name of the vGPU profile") + private String gpuCardName; + + @SerializedName(ApiConstants.MAX_VGPU_PER_PHYSICAL_GPU) + @Param(description = "the maximum number of vGPUs per physical GPU") + private Long maxVgpuPerPgpu; + + @SerializedName(ApiConstants.VIDEORAM) + @Param(description = "the video RAM size in MB") + private Long videoRam; + + @SerializedName(ApiConstants.MAXHEADS) + @Param(description = "the maximum number of display heads") + private Long maxHeads; + + @SerializedName(ApiConstants.MAXRESOLUTIONX) + @Param(description = "the maximum X resolution") + private Long maxResolutionX; + + @SerializedName(ApiConstants.MAXRESOLUTIONY) + @Param(description = "the maximum Y resolution") + private Long maxResolutionY; + + public VgpuProfileResponse(VgpuProfile vgpuProfile, GpuCard gpuCard) { + super(gpuCard); + id = vgpuProfile.getUuid(); + name = vgpuProfile.getName(); + description = vgpuProfile.getDescription(); + gpuCardId = gpuCard.getUuid(); + gpuCardName = gpuCard.getName(); + maxVgpuPerPgpu = vgpuProfile.getMaxVgpuPerPgpu(); + videoRam = vgpuProfile.getVideoRam(); + maxHeads = vgpuProfile.getMaxHeads(); + maxResolutionX = vgpuProfile.getMaxResolutionX(); + maxResolutionY = vgpuProfile.getMaxResolutionY(); + setObjectName("vgpuprofile"); + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDescription() { + return description; + } + + public String getGpuCardId() { + return gpuCardId; + } + + public String getGpuCardName() { + return gpuCardName; + } + + public Long getMaxVgpuPerPgpu() { + return maxVgpuPerPgpu; + } + + public void setDescription(String description) { + this.description = description; + } + + public Long getVideoRam() { + return videoRam; + } + + public void setVideoRam(Long videoRam) { + this.videoRam = videoRam; + } + + public Long getMaxHeads() { + return maxHeads; + } + + public void setMaxHeads(Long maxHeads) { + this.maxHeads = maxHeads; + } + + public Long getMaxResolutionX() { + return maxResolutionX; + } + + public void setMaxResolutionX(Long maxResolutionX) { + this.maxResolutionX = maxResolutionX; + } + + public Long getMaxResolutionY() { + return maxResolutionY; + } + + public void setMaxResolutionY(Long maxResolutionY) { + this.maxResolutionY = maxResolutionY; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java index 964dd22f96a9..9a171ef96ffc 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VlanIpRangeResponse.java @@ -127,9 +127,9 @@ public class VlanIpRangeResponse extends BaseResponse implements ControlledEntit @Param(description = "Indicates whether VLAN IP range is dedicated to System VMs or not") private Boolean forSystemVms; - @SerializedName(ApiConstants.FOR_NSX) - @Param(description = "indicates whether IP range is dedicated to NSX resources or not") - private Boolean forNsx; + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "indicates to which provider the IP range is dedicated to", since = "4.21.0") + private String provider; public void setId(String id) { this.id = id; @@ -249,7 +249,7 @@ public void setIp6Cidr(String ip6Cidr) { this.ip6Cidr = ip6Cidr; } - public void setForNsx(Boolean forNsx) { - this.forNsx = forNsx; + public void setProvider(String provider) { + this.provider = provider; } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java index 058ea50f991e..031d9315cd08 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VolumeResponse.java @@ -309,6 +309,18 @@ public class VolumeResponse extends BaseResponseWithTagInformation implements Co @Param(description = "the format of the disk encryption if applicable", since = "4.19.1") private String encryptionFormat; + @SerializedName(ApiConstants.KMS_KEY) + @Param(description = "KMS key name of the volume", since = "4.23.0") + private String kmsKey; + + @SerializedName(ApiConstants.KMS_KEY_ID) + @Param(description = "KMS key id of the volume", since = "4.23.0") + private String kmsKeyId; + + @SerializedName(ApiConstants.KMS_KEY_VERSION) + @Param(description = "Version number of the KMS key used for disk encryption if applicable", since = "4.23.0") + private Integer kmsKeyVersion; + public String getPath() { return path; } @@ -868,7 +880,35 @@ public void setVolumeRepairResult(Map volumeRepairResult) { this.volumeRepairResult = volumeRepairResult; } + public String getEncryptionFormat() { + return encryptionFormat; + } + public void setEncryptionFormat(String encryptionFormat) { this.encryptionFormat = encryptionFormat; } + + public String getKmsKey() { + return kmsKey; + } + + public void setKmsKey(String kmsKey) { + this.kmsKey = kmsKey; + } + + public String getKmsKeyId() { + return kmsKeyId; + } + + public void setKmsKeyId(String kmsKeyId) { + this.kmsKeyId = kmsKeyId; + } + + public Integer getKmsKeyVersion() { + return kmsKeyVersion; + } + + public void setKmsKeyVersion(Integer kmsKeyVersion) { + this.kmsKeyVersion = kmsKeyVersion; + } } diff --git a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java index a0516e660e48..2e821dae52de 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/VpcOfferingResponse.java @@ -102,6 +102,10 @@ public class VpcOfferingResponse extends BaseResponse { @Param(description = "The routing mode for the network offering, supported types are Static or Dynamic.") private String routingMode; + @SerializedName(ApiConstants.CONSERVE_MODE) + @Param(description = "True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers.", since = "4.23.0") + private Boolean conserveMode; + public void setId(String id) { this.id = id; } @@ -201,4 +205,12 @@ public String getRoutingMode() { public void setRoutingMode(String routingMode) { this.routingMode = routingMode; } + + public Boolean getConserveMode() { + return conserveMode; + } + + public void setConserveMode(Boolean conserveMode) { + this.conserveMode = conserveMode; + } } 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 2648ba836785..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 @@ -73,6 +73,10 @@ public class VpcResponse extends BaseResponseWithAnnotations implements Controll @Param(description = "VPC offering name the VPC is created from", since = "4.13.2") private String vpcOfferingName; + @SerializedName(ApiConstants.VPC_OFFERING_CONSERVE_MODE) + @Param(description = "true if VPC offering is ip conserve mode enabled", since = "4.23") + private Boolean vpcOfferingConserveMode; + @SerializedName(ApiConstants.CREATED) @Param(description = "The date this VPC was created") private Date created; @@ -181,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; } @@ -197,6 +205,10 @@ public void setDisplayText(final String displayText) { this.displayText = displayText; } + public void setVpcOfferingConserveMode(Boolean vpcOfferingConserveMode) { + this.vpcOfferingConserveMode = vpcOfferingConserveMode; + } + public void setCreated(final Date created) { this.created = created; } @@ -358,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/api/response/ZoneResponse.java b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java index ca7187dca849..a05ee925ac09 100644 --- a/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java +++ b/api/src/main/java/org/apache/cloudstack/api/response/ZoneResponse.java @@ -95,7 +95,15 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso @SerializedName("securitygroupsenabled") @Param(description = "True if security groups support is enabled, false otherwise") - private boolean securityGroupsEnabled; + private Boolean securityGroupsEnabled; + + @SerializedName("gputotal") + @Param(description = "Total GPUs in the Zone", responseObject = Long.class, since = "4.21") + private Long gpuTotal; + + @SerializedName("gpuused") + @Param(description = "Used GPUs in the Zone", responseObject = Long.class, since = "4.21") + private Long gpuUsed; @SerializedName("allocationstate") @Param(description = "The allocation state of the cluster") @@ -115,7 +123,7 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso @SerializedName(ApiConstants.LOCAL_STORAGE_ENABLED) @Param(description = "True if local storage offering enabled, false otherwise") - private boolean localStorageEnabled; + private Boolean localStorageEnabled; @SerializedName(ApiConstants.TAGS) @Param(description = "The list of resource tags associated with zone.", responseObject = ResourceTagResponse.class, since = "4.3") @@ -145,10 +153,15 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso @Param(description = "The type of the zone - core or edge", since = "4.18.0") String type; + @Deprecated(since = "4.21.0") @SerializedName(ApiConstants.NSX_ENABLED) @Param(description = "True, if zone is NSX enabled", since = "4.20.0") private boolean nsxEnabled = false; + @SerializedName(ApiConstants.PROVIDER) + @Param(description = "External network provider if any", since = "4.21.0") + private String provider = null; + @SerializedName(ApiConstants.MULTI_ARCH) @Param(description = "True, if zone contains clusters and hosts from different CPU architectures", since = "4.20") private boolean multiArch; @@ -161,11 +174,19 @@ public class ZoneResponse extends BaseResponseWithAnnotations implements SetReso @Param(description = "True, if routed Network/VPC is enabled", since = "4.20.1") private boolean routedModeEnabled = false; + @SerializedName(ApiConstants.STORAGE_ACCESS_GROUPS) + @Param(description = "comma-separated list of storage access groups for the zone", since = "4.21.0") + private String storageAccessGroups; + public ZoneResponse() { tags = new LinkedHashSet(); } + public ZoneResponse(Set tags) { + this.tags = tags; + } + public void setId(String id) { this.id = id; } @@ -218,6 +239,14 @@ public void setSecurityGroupsEnabled(boolean securityGroupsEnabled) { this.securityGroupsEnabled = securityGroupsEnabled; } + public void setGpuTotal(Long gpuTotal) { + this.gpuTotal = gpuTotal; + } + + public void setGpuUsed(Long gpuUsed) { + this.gpuUsed = gpuUsed; + } + public void setAllocationState(String allocationState) { this.allocationState = allocationState; } @@ -353,6 +382,14 @@ public boolean isSecurityGroupsEnabled() { return securityGroupsEnabled; } + public Long getGpuUsed() { + return gpuUsed; + } + + public Long getGpuTotal() { + return gpuTotal; + } + public boolean isLocalStorageEnabled() { return localStorageEnabled; } @@ -373,6 +410,14 @@ public boolean isNsxEnabled() { return nsxEnabled; } + public String getProvider() { + return provider; + } + + public void setProvider(String provider) { + this.provider = provider; + } + @Override public void setResourceIconResponse(ResourceIconResponse resourceIconResponse) { this.resourceIconResponse = resourceIconResponse; @@ -402,6 +447,14 @@ public String getType() { return type; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + public void setNsxEnabled(boolean nsxEnabled) { this.nsxEnabled = nsxEnabled; } diff --git a/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java b/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java index ee3b98b8a4b6..cccc1fff9823 100644 --- a/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java +++ b/api/src/main/java/org/apache/cloudstack/auth/UserOAuth2Authenticator.java @@ -42,8 +42,19 @@ public interface UserOAuth2Authenticator extends Adapter { * Verifies the code provided by provider and fetches email * @return returns email */ - String verifyCodeAndFetchEmail(String secretCode); + String verifySecretCodeAndFetchEmail(String secretCode); + /** + * Verifies if the logged in user is valid for a specific domain + * @return true if it's a valid user, otherwise false + */ + boolean verifyUser(String email, String secretCode, Long domainId); + + /** + * Verifies the secret code provided by provider and fetches email for a specific domain + * @return email for the specified domain + */ + String verifySecretCodeAndFetchEmail(String secretCode, Long domainId); /** * Fetches email using the accessToken diff --git a/api/src/main/java/org/apache/cloudstack/backup/Backup.java b/api/src/main/java/org/apache/cloudstack/backup/Backup.java index f21f20adb33e..2124423da108 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/Backup.java +++ b/api/src/main/java/org/apache/cloudstack/backup/Backup.java @@ -17,8 +17,10 @@ package org.apache.cloudstack.backup; +import java.io.Serializable; import java.util.Date; import java.util.List; +import java.util.Map; import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.Identity; @@ -29,8 +31,36 @@ public interface Backup extends ControlledEntity, InternalIdentity, Identity { + String getFromCheckpointId(); + + String getToCheckpointId(); + + Long getCheckpointCreateTime(); + + Long getHostId(); + enum Status { - Allocated, Queued, BackingUp, BackedUp, Error, Failed, Restoring, Removed, Expunged + Allocated, Queued, BackingUp, ReadyForImageTransfer, FinalizingImageTransfer, BackedUp, Error, Failed, Restoring, Removed, Expunged, + // Hidden: a chain backup kept as a tombstone after the user deleted it while it still has + // live descendants (incremental chains). Excluded from listBackups and from all backup + // operations (which require BackedUp); swept from the DB once its last descendant is gone. + Hidden + } + + enum CompressionStatus { + Uncompressed, Compressing, FinalizingCompression, Compressed, CompressionError + } + + enum ValidationStatus { + NotValidated, Validating, Valid, UnableToValidate, NotValid + } + + enum ValidationSteps { + wait_for_boot, screenshot, execute_command + } + + enum CompressionLibrary { + zstd, zlib } class Metric { @@ -63,6 +93,8 @@ class RestorePoint { private String id; private Date created; private String type; + private Long backupSize = 0L; + private Long dataSize = 0L; public RestorePoint(String id, Date created, String type) { this.id = id; @@ -70,6 +102,12 @@ public RestorePoint(String id, Date created, String type) { this.type = type; } + public RestorePoint(String id, Date created, String type, Long backupSize, Long dataSize) { + this(id, created, type); + this.backupSize = backupSize; + this.dataSize = dataSize; + } + public String getId() { return id; } @@ -93,19 +131,43 @@ public String getType() { public void setType(String type) { this.type = type; } + + public Long getBackupSize() { + return backupSize; + } + + public void setBackupSize(Long backupSize) { + this.backupSize = backupSize; + } + + public Long getDataSize() { + return dataSize; + } + + public void setDataSize(Long dataSize) { + this.dataSize = dataSize; + } } - class VolumeInfo { + class VolumeInfo implements Serializable { private String uuid; private Volume.Type type; private Long size; private String path; + private Long deviceId; + private String diskOfferingId; + private Long minIops; + private Long maxIops; - public VolumeInfo(String uuid, String path, Volume.Type type, Long size) { + public VolumeInfo(String uuid, String path, Volume.Type type, Long size, Long deviceId, String diskOfferingId, Long minIops, Long maxIops) { this.uuid = uuid; this.type = type; this.size = size; this.path = path; + this.deviceId = deviceId; + this.diskOfferingId = diskOfferingId; + this.minIops = minIops; + this.maxIops = maxIops; } public String getUuid() { @@ -128,20 +190,45 @@ public Long getSize() { return size; } + public Long getDeviceId() { + return deviceId; + } + + public String getDiskOfferingId() { + return diskOfferingId; + } + + public Long getMinIops() { + return minIops; + } + + public Long getMaxIops() { + return maxIops; + } + @Override public String toString() { - return StringUtils.join(":", uuid, path, type, size); + return StringUtils.join(":", uuid, path, type, size, deviceId, diskOfferingId, minIops, maxIops); } } - long getVmId(); + Long getVmId(); long getBackupOfferingId(); String getExternalId(); String getType(); Date getDate(); Backup.Status getStatus(); + Backup.CompressionStatus getCompressionStatus(); + Backup.ValidationStatus getValidationStatus(); Long getSize(); Long getProtectedSize(); + void setName(String name); + String getDescription(); + void setDescription(String description); + Long getUncompressedSize(); List getBackedUpVolumes(); long getZoneId(); + Map getDetails(); + String getDetail(String name); + Long getBackupScheduleId(); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java index 0090b4f6b16c..30fc2bbec40d 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupManager.java @@ -18,20 +18,32 @@ package org.apache.cloudstack.backup; import java.util.List; +import java.util.Map; +import com.cloud.storage.Volume; +import com.cloud.vm.VirtualMachine; +import com.cloud.capacity.Capacity; +import com.cloud.exception.ResourceAllocationException; +import org.apache.cloudstack.api.command.admin.backup.CloneBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.ImportBackupOfferingCmd; import org.apache.cloudstack.api.command.admin.backup.UpdateBackupOfferingCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupCmd; import org.apache.cloudstack.api.command.user.backup.CreateBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.DeleteBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupOfferingsCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupScheduleCmd; import org.apache.cloudstack.api.command.user.backup.ListBackupsCmd; +import org.apache.cloudstack.api.command.user.backup.CreateBackupOfferingCmd; +import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.framework.config.ConfigKey; -import org.apache.cloudstack.framework.config.ValidatedConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.network.Network; import com.cloud.utils.Pair; import com.cloud.utils.component.Manager; import com.cloud.utils.component.PluggableService; +import com.cloud.vm.VmDiskInfo; /** * Backup and Recover Manager Interface @@ -43,11 +55,11 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer "false", "Is backup and recovery framework enabled.", false, ConfigKey.Scope.Zone); - ConfigKey BackupProviderPlugin = new ValidatedConfigKey<>("Advanced", String.class, + ConfigKey BackupProviderPlugin = new ConfigKey<>("Advanced", String.class, "backup.framework.provider.plugin", "dummy", - "The backup and recovery provider plugin. Valid plugin values: dummy, veeam, networker and nas", - true, ConfigKey.Scope.Zone, BackupFrameworkEnabled.key(), value -> validateBackupProviderConfig((String)value)); + "The backup and recovery provider plugin.", + true, ConfigKey.Scope.Zone, BackupFrameworkEnabled.key()); ConfigKey BackupSyncPollingInterval = new ConfigKey<>("Advanced", Long.class, "backup.framework.sync.interval", @@ -59,6 +71,62 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer "false", "Enable volume attach/detach operations for VMs that are assigned to Backup Offerings.", true); + ConfigKey DefaultMaxAccountBackups = new ConfigKey("Account Defaults", Long.class, + "max.account.backups", + "20", + "The default maximum number of backups that can be created for an account", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxAccountBackupStorage = new ConfigKey("Account Defaults", Long.class, + "max.account.backup.storage", + "400", + "The default maximum backup storage space (in GiB) that can be used for an account", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxProjectBackups = new ConfigKey("Project Defaults", Long.class, + "max.project.backups", + "20", + "The default maximum number of backups that can be created for a project", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxProjectBackupStorage = new ConfigKey("Project Defaults", Long.class, + "max.project.backup.storage", + "400", + "The default maximum backup storage space (in GiB) that can be used for a project", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxDomainBackups = new ConfigKey("Domain Defaults", Long.class, + "max.domain.backups", + "40", + "The default maximum number of backups that can be created for a domain", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxDomainBackupStorage = new ConfigKey("Domain Defaults", Long.class, + "max.domain.backup.storage", + "800", + "The default maximum backup storage space (in GiB) that can be used for a domain", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey BackupStorageCapacityThreshold = new ConfigKey<>("Alert", Float.class, + "zone.backupStorage.capacity.notificationthreshold", + "0.75", + "Percentage (as a value between 0 and 1) of backup storage utilization above which alerts will be sent about low storage available.", + true, + ConfigKey.Scope.Zone, + null); + /** * List backup provider offerings * @param zoneId zone id @@ -71,6 +139,20 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer */ BackupOffering importBackupOffering(final ImportBackupOfferingCmd cmd); + /** + * Create a new Backup and Recovery policy to CloudStack. Currently only supported for KBOSS. + * @param cmd create backup offering cmd + */ + BackupOffering createBackupOffering(final CreateBackupOfferingCmd cmd); + + List getBackupOfferingDomains(final Long offeringId); + + /** + * Clone an existing backup offering with updated values + * @param cmd clone backup offering cmd + */ + BackupOffering cloneBackupOffering(final CloneBackupOfferingCmd cmd); + /** * List backup offerings * @param ListBackupOfferingsCmd API cmd @@ -110,7 +192,7 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer * @param vmId * @return */ - List listBackupSchedule(Long vmId); + List listBackupSchedules(ListBackupScheduleCmd cmd); /** * Deletes VM backup schedule for a VM @@ -121,10 +203,11 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer /** * Creates backup of a VM - * @param vmId Virtual Machine ID + * @param cmd CreateBackupCmd + * @param job The async job associated with the backup retention * @return returns operation success */ - boolean createBackup(final Long vmId); + boolean createBackup(CreateBackupCmd cmd, Object job) throws ResourceAllocationException; /** * List existing backups for a VM @@ -134,12 +217,23 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer /** * Restore a full VM from backup */ - boolean restoreBackup(final Long backupId); + boolean restoreBackup(final Long backupId, boolean quickRestore, Long hostId); + + Map getIpToNetworkMapFromBackup(Backup backup, boolean preserveIps, List networkIds); + + Boolean canCreateInstanceFromBackup(Long backupId); + + Boolean canCreateInstanceFromBackupAcrossZones(Long backupId); + + /** + * Restore a backup to a new Instance + */ + boolean restoreBackupToVM(Long backupId, Long vmId, boolean quickrestore) throws ResourceUnavailableException; /** * Restore a backed up volume and attach it to a VM */ - boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId) throws Exception; + boolean restoreBackupVolumeAndAttachToVM(final String backedUpVolumeUuid, final Long backupId, final Long vmId, boolean isQuickRestore, Long hostId) throws Exception; /** * Deletes a backup @@ -147,17 +241,27 @@ public interface BackupManager extends BackupService, Configurable, PluggableSer * @param forced Indicates if backup will be force removed or not * @return returns operation success */ - boolean deleteBackup(final Long backupId, final Boolean forced); + boolean deleteBackup(final Long backupId, final Boolean forced) throws ResourceAllocationException; + + void validateBackupForZone(Long zoneId); BackupOffering updateBackupOffering(UpdateBackupOfferingCmd updateBackupOfferingCmd); - static void validateBackupProviderConfig(String value) { - if (value != null && (value.contains(",") || value.trim().contains(" "))) { - throw new IllegalArgumentException("Multiple backup provider plugins are not supported. Please provide a single plugin value."); - } - List validPlugins = List.of("dummy", "veeam", "networker", "nas"); - if (value != null && !validPlugins.contains(value)) { - throw new IllegalArgumentException("Invalid backup provider plugin: " + value + ". Valid plugin values are: " + String.join(", ", validPlugins)); - } - } + VmDiskInfo getRootDiskInfoFromBackup(Backup backup); + + List getDataDiskInfoListFromBackup(Backup backup); + + void checkVmDisksSizeAgainstBackup(List vmDiskInfoList, Backup backup); + + Map getBackupDetailsFromVM(VirtualMachine vm); + + String createVolumeInfoFromVolumes(List vmVolumes); + + String getBackupNameFromVM(VirtualMachine vm); + + BackupResponse createBackupResponse(Backup backup, Boolean listVmDetails); + + Capacity getBackupStorageUsedStats(Long zoneId); + + void checkAndRemoveBackupOfferingBeforeExpunge(VirtualMachine vm); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java index d36dfb7360f6..ffc32b6c4d08 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupProvider.java @@ -17,13 +17,14 @@ package org.apache.cloudstack.backup; import java.util.List; -import java.util.Map; import com.cloud.utils.Pair; import com.cloud.vm.VirtualMachine; public interface BackupProvider { + Boolean crossZoneInstanceCreationEnabled(BackupOffering backupOffering); + /** * Returns the unique name of the provider * @return returns provider name @@ -49,22 +50,32 @@ public interface BackupProvider { /** * Assign a VM to a backup offering or policy - * @param vm - * @param backup - * @param policy - * @return + * @param vm the machine to back up + * @param backupOffering the SLA definition for the backup + * @return succeeded? */ boolean assignVMToBackupOffering(VirtualMachine vm, BackupOffering backupOffering); /** * Removes a VM from a backup offering or policy - * @param vm - * @return + * @param vm the machine to stop backing up + * @return succeeded? */ boolean removeVMFromBackupOffering(VirtualMachine vm); /** - * Whether the provide will delete backups on removal of VM from the offfering + * Removes the specified backup schedule from a virtual machine. + * + * @param vm the virtual machine from which the schedule will be removed. + * @param backupSchedule the backup schedule to be removed. + * @return {@code true} if the operation was successful; {@code false} otherwise. + */ + default boolean removeVMBackupSchedule(VirtualMachine vm, BackupSchedule backupSchedule) { + return true; + } + + /** + * Whether the provider will delete backups on removal of VM from the offering * @return boolean result */ boolean willDeleteBackupsOnOfferingRemoval(); @@ -72,41 +83,88 @@ public interface BackupProvider { /** * Starts and creates an adhoc backup process * for a previously registered VM backup - * @param backup - * @return + * + * @param vm + * the machine to make a backup of + * @param quiesceVM + * instance will be quiesced for checkpointing for backup. Applicable only to NAS plugin. + * @param isolated + * @return the result and {code}Backup{code} {code}Object{code} */ - boolean takeBackup(VirtualMachine vm); + Pair takeBackup(VirtualMachine vm, Boolean quiesceVM, boolean isolated, Long backupScheduleId); /** * Delete an existing backup - * @param backuo The backup to exclude + * @param backup The backup to exclude * @param forced Indicates if backup will be force removed or not - * @return + * @return succeeded? */ boolean deleteBackup(Backup backup, boolean forced); + /** + * Whether {@link #deleteBackup(Backup, boolean)} owns DB-row removal and resource-count / + * usage accounting for every backup it physically removes. Providers that manage incremental + * chains (e.g. NAS) delete several backups per call — the leaf plus swept delete-pending + * ancestors — and decrement once per removed backup themselves, so the manager must NOT + * decrement or remove the row again. Defaults to {@code false}: the manager does the + * single-backup accounting (the historical behaviour for non-chain providers). + */ + default boolean handlesChainDeleteResourceAccounting() { + return false; + } + + Pair restoreBackupToVM(VirtualMachine vm, Backup backup, String hostIp, String dataStoreUuid, boolean quickrestore); + /** * Restore VM from backup */ - boolean restoreVMFromBackup(VirtualMachine vm, Backup backup); + boolean restoreVMFromBackup(VirtualMachine vm, Backup backup, boolean quickRestore, Long hostId); /** * Restore a volume from a backup */ - Pair restoreBackedUpVolume(Backup backup, String volumeUuid, String hostIp, String dataStoreUuid, Pair vmNameAndState); + Pair restoreBackedUpVolume(Backup backup, Backup.VolumeInfo backupVolumeInfo, String hostIp, String dataStoreUuid, + Pair vmNameAndState, VirtualMachine vm, boolean quickRestore); /** - * Returns backup metrics for a list of VMs in a zone - * @param zoneId - * @param vms - * @return + * Syncs backup metrics (backup size, protected size) from the plugin and stores it within the provider + * @param zoneId the zone for which to return metrics */ - Map getBackupMetrics(Long zoneId, List vms); + void syncBackupMetrics(Long zoneId); /** - * This method should reconcile and create backup entries for any backups created out-of-band - * @param vm - * @param metric + * Returns a list of Backup.RestorePoint + * @param vm the machine to get the restore points for + */ + List listRestorePoints(VirtualMachine vm); + + /** + * Creates and returns an entry in the backups table by getting the information from restorePoint and vm. + * + * @param restorePoint the restore point to create a backup for + * @param vm The machine for which to create a backup + */ + Backup createNewBackupEntryForRestorePoint(Backup.RestorePoint restorePoint, VirtualMachine vm); + + /** + * Returns if the backup provider supports creating new instance from backup + */ + boolean supportsInstanceFromBackup(); + + default boolean supportsMemoryVmSnapshot() { + return true; + } + + /** + * Returns the backup storage usage (Used, Total) for a backup provider + * @param zoneId the zone for which to return metrics + * @return a pair of Used size and Total size for the backup storage + */ + Pair getBackupStorageStats(Long zoneId); + + /** + * Gets the backup storage usage (Used, Total) from the plugin and stores it in db + * @param zoneId the zone for which to return metrics */ - void syncBackups(VirtualMachine vm, Backup.Metric metric); + void syncBackupStorageStats(Long zoneId); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupRepository.java b/api/src/main/java/org/apache/cloudstack/backup/BackupRepository.java index 8e5c9740e690..886d13c13f93 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupRepository.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupRepository.java @@ -28,7 +28,12 @@ public interface BackupRepository extends InternalIdentity, Identity { String getType(); String getAddress(); String getMountOptions(); + void setMountOptions(String mountOptions); + void setUsedBytes(Long usedBytes); Long getCapacityBytes(); Long getUsedBytes(); + void setCapacityBytes(Long capacityBytes); + Boolean crossZoneInstanceCreationEnabled(); + void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation); Date getCreated(); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupRepositoryService.java b/api/src/main/java/org/apache/cloudstack/backup/BackupRepositoryService.java index ae71053e400d..cc8144ebe403 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupRepositoryService.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupRepositoryService.java @@ -23,12 +23,13 @@ import org.apache.cloudstack.api.command.user.backup.repository.AddBackupRepositoryCmd; import org.apache.cloudstack.api.command.user.backup.repository.DeleteBackupRepositoryCmd; import org.apache.cloudstack.api.command.user.backup.repository.ListBackupRepositoriesCmd; +import org.apache.cloudstack.api.command.user.backup.repository.UpdateBackupRepositoryCmd; import java.util.List; public interface BackupRepositoryService { BackupRepository addBackupRepository(AddBackupRepositoryCmd cmd); + BackupRepository updateBackupRepository(UpdateBackupRepositoryCmd cmd); boolean deleteBackupRepository(DeleteBackupRepositoryCmd cmd); Pair, Integer> listBackupRepositories(ListBackupRepositoriesCmd cmd); - } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java index d81dd731b1ff..ddc823a14ff5 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupSchedule.java @@ -19,15 +19,20 @@ import java.util.Date; +import org.apache.cloudstack.acl.ControlledEntity; import org.apache.cloudstack.api.InternalIdentity; import com.cloud.utils.DateUtil; -public interface BackupSchedule extends InternalIdentity { +public interface BackupSchedule extends ControlledEntity, InternalIdentity { Long getVmId(); DateUtil.IntervalType getScheduleType(); String getSchedule(); String getTimezone(); Date getScheduledTimestamp(); Long getAsyncJobId(); + Boolean getQuiesceVM(); + int getMaxBackups(); + String getUuid(); + boolean isIsolated(); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/BackupService.java b/api/src/main/java/org/apache/cloudstack/backup/BackupService.java index d4beb629fe0f..3ba2978c0fa5 100644 --- a/api/src/main/java/org/apache/cloudstack/backup/BackupService.java +++ b/api/src/main/java/org/apache/cloudstack/backup/BackupService.java @@ -34,4 +34,11 @@ public interface BackupService { * @return backup provider */ BackupProvider getBackupProvider(final Long zoneId); + + /** + * Find backup provider by name + * @param name backup provider name + * @return backup provider + */ + BackupProvider getBackupProvider(final String name); } diff --git a/api/src/main/java/org/apache/cloudstack/backup/ImageTransfer.java b/api/src/main/java/org/apache/cloudstack/backup/ImageTransfer.java new file mode 100644 index 000000000000..e1153be3ae02 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/ImageTransfer.java @@ -0,0 +1,61 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface ImageTransfer extends ControlledEntity, InternalIdentity { + long getDataCenterId(); + + public enum Direction { + upload, download + } + + public enum Format { + raw, + cow + } + + public enum Backend { + nbd, + file + } + + public enum Phase { + initializing, transferring, finished, failed + } + + String getUuid(); + + Long getBackupId(); + + long getVolumeId(); + + long getHostId(); + + String getTransferUrl(); + + Phase getPhase(); + + Direction getDirection(); + + Backend getBackend(); + + String getSignedTicketId(); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/InternalBackupProvider.java b/api/src/main/java/org/apache/cloudstack/backup/InternalBackupProvider.java new file mode 100644 index 000000000000..efd28385a3e8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/InternalBackupProvider.java @@ -0,0 +1,142 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.storage.Volume; +import com.cloud.uservm.UserVm; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.snapshot.VMSnapshot; +import org.apache.cloudstack.framework.config.ConfigKey; + +import java.util.Set; + +public interface InternalBackupProvider extends BackupProvider { + String VM_WORK_JOB_HANDLER = InternalBackupService.class.getSimpleName(); + + ConfigKey backupCompressionTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.compression.timeout", "28800", "Backup compression timeout (in " + + "seconds). Will only start counting once the backup compression async job actually starts. This setting is currently only applicable to KBOSS.", true, + ConfigKey.Scope.Cluster); + + ConfigKey backupCompressionMinimumFreeStorage = new ConfigKey<>("Advanced", Double.class, "backup.compression.minimum.free.storage", "1", "The minimum " + + "amount of free storage that should be available to start the compression. This configuration uses a multiplier on the backup size, by default, it needs the same " + + "amount of free storage as the backup uses while uncompressed. This setting is currently only applicable to KBOSS.", true, ConfigKey.Scope.Zone); + + ConfigKey backupCompressionCoroutines = new ConfigKey<>("Advanced", Integer.class, "backup.compression.coroutines", "1", "Number of parallel coroutines " + + "for the compression process. This is translated to qemu-img '-m' parameter. This setting is currently only applicable to KBOSS.", true, ConfigKey.Scope.Cluster); + + ConfigKey backupCompressionRateLimit = new ConfigKey<>("Advanced", Integer.class, "backup.compression.rate.limit", "0", "Limit the compression rate to " + + "this configuration's value (in MB/s). Values lower than 1 disable the limit. This setting is currently only applicable to KBOSS.", true, ConfigKey.Scope.Cluster); + + ConfigKey backupValidationTimeout = new ConfigKey<>("Advanced", Integer.class, "backup.validation.timeout", "3600", "Backup validation job timeout (in " + + "seconds). Will only start counting once the backup validation async job actually starts. This setting is currently only applicable to KBOSS.", true, ConfigKey.Scope.Cluster); + + /** + * Actually execute the backup after being queued. + * */ + default Pair orchestrateTakeBackup(Backup backup, boolean quiesceVm, boolean isolated) { + return null; + } + + /** + * Actually delete the backup after being queued. + * */ + default Boolean orchestrateDeleteBackup(Backup backup, boolean forced) { + return null; + } + + /** + * Actually restore the backup after being queued. + * */ + default Boolean orchestrateRestoreVMFromBackup(Backup backup, VirtualMachine vm, boolean quickRestore, Long hostId, boolean sameVmAsBackup) { + return null; + } + + /** + * This method should be overwritten by any backup providers that want to schedule their backup restore jobs in the same queue as the VM jobs. + * Otherwise, just use the restoreBackedUpVolume method. + * */ + default Pair orchestrateRestoreBackedUpVolume(Backup backup, VirtualMachine vm, Backup.VolumeInfo backupVolumeInfo, String hostIp, boolean quickRestore) { + return null; + } + + /** + * This method should be overwritten by any native backup providers that want to allow backup compression through ACS.
+ * The compression is done in two steps:
+ * 1) Compress the backup to a different file;
+ * 2) Switch the old file for the newly compressed one.
+ *

+ * This method is supposed to execute step 1. + * + * @return + */ + default boolean startBackupCompression(long backupId, long hostId) { + return false; + } + + /** + * This method should be overwritten by any native backup providers that want to allow backup compression through ACS.
+ * The compression is done in two steps:
+ * 1) Compress the backup to a different file;
+ * 2) Switch the old file for the newly compressed one.
+ *

+ * This method is supposed to execute step 2. + * + * @return + */ + default boolean finalizeBackupCompression(long backupId, long hostId) { + return false; + } + + default boolean validateBackup(long backupId, long hostId) { + return false; + } + + /** + * This method should be overwritten by any native backup providers that allow volume detach but need to prepare it beforehand. + * */ + default void prepareVolumeForDetach(Volume volume, VirtualMachine virtualMachine) { + } + + /** + * This method should be overwritten by any native backup providers that allow volume migration but need to prepare it beforehand. + * */ + default void prepareVolumeForMigration(Volume volume, VirtualMachine virtualMachine) { + } + + /** + * This method should be overwritten by any native backup providers that must update metadata regarding a volume after certain operations (such as after a volume migration). + * */ + default void updateVolumeId(VirtualMachine virtualMachine, long oldVolumeId, long newVolumeId) { + } + + default Set getSecondaryStorageUrls(UserVm userVm) { + return Set.of(); + } + + /** + * This method should be overwritten by any native backup providers that are compatible with VM Snapshots but need to prepare the VM to be reverted. + * Currently, the only strategy that calls this method is the {@code KvmFileBasedStorageVmSnapshotStrategy}. + * */ + default void prepareVmForSnapshotRevert(VMSnapshot vmSnapshot, VirtualMachine virtualMachine) { + } + + default boolean finishBackupChains(VirtualMachine virtualMachine) { + return false; + } +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/InternalBackupService.java b/api/src/main/java/org/apache/cloudstack/backup/InternalBackupService.java new file mode 100644 index 000000000000..76c71f0eb4ce --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/InternalBackupService.java @@ -0,0 +1,54 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.to.DataTO; +import com.cloud.storage.Volume; +import com.cloud.uservm.UserVm; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.snapshot.VMSnapshot; +import org.apache.cloudstack.api.response.ExtractResponse; + +import java.util.Set; + +public interface InternalBackupService { + + void configureChainInfo(DataTO volumeTo, Command cmd); + + void cleanupBackupMetadata(long volumeId); + + void prepareVolumeForDetach(Volume volume, VirtualMachine virtualMachine); + + void prepareVolumeForMigration(Volume volume); + + void prepareVmForSnapshotRevert(VMSnapshot vmSnapshot); + + void updateVolumeId(long oldVolumeId, long newVolumeId); + + Set getSecondaryStorageUrls(UserVm userVm); + + boolean startBackupCompression(long backupId, long hostId, long zoneId); + + boolean finalizeBackupCompression(long backupId, long hostId, long zoneId); + + boolean validateBackup(long backupId, long hostId, long zoneId); + + ExtractResponse downloadScreenshot(long backupId); + + boolean finishBackupChain(long vmId); +} diff --git a/api/src/main/java/org/apache/cloudstack/backup/KVMBackupExportService.java b/api/src/main/java/org/apache/cloudstack/backup/KVMBackupExportService.java new file mode 100644 index 000000000000..e1affb311ec6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/backup/KVMBackupExportService.java @@ -0,0 +1,106 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.List; + +import org.apache.cloudstack.api.command.admin.backup.CreateImageTransferCmd; +import org.apache.cloudstack.api.command.admin.backup.DeleteVmCheckpointCmd; +import org.apache.cloudstack.api.command.admin.backup.FinalizeBackupCmd; +import org.apache.cloudstack.api.command.admin.backup.FinalizeImageTransferCmd; +import org.apache.cloudstack.api.command.admin.backup.ListImageTransfersCmd; +import org.apache.cloudstack.api.command.admin.backup.ListVmCheckpointsCmd; +import org.apache.cloudstack.api.command.admin.backup.StartBackupCmd; +import org.apache.cloudstack.api.response.CheckpointResponse; +import org.apache.cloudstack.api.response.ImageTransferResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; + +import com.cloud.utils.component.PluggableService; + +/** + * Service for Creating Backups and ImageTransfer sessions which will be consumed by an external orchestrator. + */ +public interface KVMBackupExportService extends Configurable, PluggableService { + + ConfigKey ImageTransferIdleTimeoutSeconds = new ConfigKey<>("Advanced", Integer.class, + "image.transfer.idle.timeout.seconds", + "600", + "Seconds since last completed HTTP request to an image transfer before the image server unregisters it (idle timeout).", + true, ConfigKey.Scope.Zone); + + ConfigKey ExposeKVMBackupExportServiceApis = new ConfigKey<>("Advanced", Boolean.class, + "expose.kvm.backup.export.service.apis", + "false", + "Enable to expose APIs for testing the KVM Backup Export Service.", + false, ConfigKey.Scope.Global); + /** + * Creates a backup session for a VM + */ + Backup createBackup(StartBackupCmd cmd); + + /** + * Start a backup session for a VM + * Creates a new checkpoint and starts NBD server for pull-mode backup + */ + Backup startBackup(StartBackupCmd cmd); + + /** + * Finalize a backup session + * Stops NBD server, updates checkpoint tracking, deletes old checkpoints + */ + Backup finalizeBackup(FinalizeBackupCmd cmd); + + /** + * Create an image transfer object for a disk + * Registers NBD endpoint with ImageIO (stubbed for POC) + */ + ImageTransferResponse createImageTransfer(CreateImageTransferCmd cmd); + + ImageTransfer createImageTransfer(long volumeId, Long backupId, ImageTransfer.Direction direction, ImageTransfer.Format format); + + boolean cancelImageTransfer(long imageTransferId); + + /** + * Finalize an image transfer + * Marks transfer as complete (NBD is closed globally in finalize backup) + */ + boolean finalizeImageTransfer(FinalizeImageTransferCmd cmd); + + boolean finalizeImageTransfer(long imageTransferId); + + /** + * List image transfers for a backup + */ + List listImageTransfers(ListImageTransfersCmd cmd); + + /** + * List checkpoints for a VM + */ + List listVmCheckpoints(ListVmCheckpointsCmd cmd); + + /** + * Delete a VM checkpoint (no-op for normal flow, kept for API parity) + */ + boolean deleteVmCheckpoint(DeleteVmCheckpointCmd cmd); + + /** + * List Compatible Data Center Ids for the service + */ + List listCompatibleDataCenterIds(); +} 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/main/java/org/apache/cloudstack/command/ReconcileCommandService.java b/api/src/main/java/org/apache/cloudstack/command/ReconcileCommandService.java new file mode 100644 index 000000000000..89ab97990df7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/command/ReconcileCommandService.java @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.command; + + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.hypervisor.Hypervisor; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.framework.config.ConfigKey; + +import java.util.Arrays; +import java.util.List; + +public interface ReconcileCommandService { + + ConfigKey ReconcileCommandsEnabled = new ConfigKey<>("Advanced", Boolean.class, + "reconcile.commands.enabled", "false", + "Indicates whether the background task to reconcile the commands is enabled or not", + false); + + ConfigKey ReconcileCommandsInterval = new ConfigKey<>("Advanced", Integer.class, + "reconcile.commands.interval", "60", + "Interval (in seconds) for the background task to reconcile the commands", + false); + ConfigKey ReconcileCommandsMaxAttempts = new ConfigKey<>("Advanced", Integer.class, + "reconcile.commands.max.attempts", "30", + "The maximum number of attempts to reconcile the commands", + true); + + ConfigKey ReconcileCommandsWorkers = new ConfigKey<>("Advanced", Integer.class, + "reconcile.commands.workers", "100", + "The Number of worker threads to reconcile the commands", + false); + + List SupportedHypervisorTypes = Arrays.asList(Hypervisor.HypervisorType.KVM); + + void persistReconcileCommands(Long hostId, Long requestSequence, Command[] cmd); + + boolean updateReconcileCommand(long requestSeq, Command command, Answer answer, Command.State newStateByManagement, Command.State newStateByAgent); + + void processCommand(Command pingCommand, Answer pingAnswer); + + void processAnswers(long requestSeq, Command[] commands, Answer[] answers); + + void updateReconcileCommandToInterruptedByManagementServerId(long managementServerId); + + void updateReconcileCommandToInterruptedByHostId(long hostId); + + boolean isReconcileResourceNeeded(long resourceId, ApiCommandResourceType resourceType); +} diff --git a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java index a4aa860487f3..113b97f43c8f 100644 --- a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java +++ b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java @@ -16,10 +16,15 @@ // under the License. package org.apache.cloudstack.config; +import com.cloud.exception.InvalidParameterValueException; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; +import org.apache.commons.lang3.StringUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ApiServiceConfiguration implements Configurable { + protected static Logger LOGGER = LogManager.getLogger(ApiServiceConfiguration.class); public static final ConfigKey ManagementServerAddresses = new ConfigKey<>(String.class, "host", "Advanced", "localhost", "The ip address of management server. This can also accept comma separated addresses.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); public static final ConfigKey ApiServletPath = new ConfigKey("Advanced", String.class, "endpoint.url", "http://localhost:8080/client/api", "API end point. Can be used by CS components/services deployed remotely, for sending CS API requests", true); @@ -29,6 +34,20 @@ public class ApiServiceConfiguration implements Configurable { "true", "Are the source checks on API calls enabled (true) or not (false)? See api.allowed.source.cidr.list", true, ConfigKey.Scope.Global); public static final ConfigKey ApiAllowedSourceCidrList = new ConfigKey<>(String.class, "api.allowed.source.cidr.list", "Advanced", "0.0.0.0/0,::/0", "Comma separated list of IPv4/IPv6 CIDRs from which API calls can be performed. Can be set on Global and Account levels.", true, ConfigKey.Scope.Account, null, null, null, null, null, ConfigKey.Kind.CSV, null); + + + public static void validateEndpointUrl() { + String csUrl = getApiServletPathValue(); + if (StringUtils.isBlank(csUrl) || StringUtils.containsAny(csUrl, "localhost", "127.0.0.1", "[::1]")) { + LOGGER.error("Global setting [{}] cannot contain localhost or be blank. Current value: {}", ApiServletPath.key(), csUrl); + throw new InvalidParameterValueException("Unable to complete this operation. Contact your cloud admin."); + } + } + + public static String getApiServletPathValue() { + return ApiServletPath.value(); + } + @Override public String getConfigComponentName() { return ApiServiceConfiguration.class.getSimpleName(); diff --git a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java index 5bd9699b2019..655b8faf443a 100644 --- a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java +++ b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleAccessManager.java @@ -18,6 +18,9 @@ import com.cloud.utils.component.Manager; import org.apache.cloudstack.api.command.user.consoleproxy.ConsoleEndpoint; +import org.apache.cloudstack.api.command.user.consoleproxy.ListConsoleSessionsCmd; +import org.apache.cloudstack.api.response.ConsoleSessionResponse; +import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; import java.util.Date; @@ -44,8 +47,12 @@ public interface ConsoleAccessManager extends Manager, Configurable { void removeSessions(String[] sessionUuids); - void acquireSession(String sessionUuid); + void acquireSession(String sessionUuid, String clientAddress); String genAccessTicket(String host, String port, String sid, String tag, String sessionUuid); String genAccessTicket(String host, String port, String sid, String tag, Date normalizedHashTime, String sessionUuid); + + ListResponse listConsoleSessions(ListConsoleSessionsCmd cmd); + + ConsoleSession listConsoleSessionById(long id); } diff --git a/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java new file mode 100644 index 000000000000..6cbdd31fd943 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/consoleproxy/ConsoleSession.java @@ -0,0 +1,45 @@ +// 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.consoleproxy; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface ConsoleSession extends InternalIdentity, Identity { + + Date getCreated(); + + long getDomainId(); + + long getAccountId(); + + long getUserId(); + + long getInstanceId(); + + long getHostId(); + + Date getRemoved(); + + Date getAcquired(); + + String getConsoleEndpointCreatorAddress(); + + String getClientAddress(); +} diff --git a/api/src/main/java/org/apache/cloudstack/context/CallContext.java b/api/src/main/java/org/apache/cloudstack/context/CallContext.java index 69376e4f6d7d..fcfb5b6b1e00 100644 --- a/api/src/main/java/org/apache/cloudstack/context/CallContext.java +++ b/api/src/main/java/org/apache/cloudstack/context/CallContext.java @@ -63,6 +63,7 @@ protected Stack initialValue() { private User user; private long userId; private final Map context = new HashMap(); + private final Map apiResourcesUuids = new HashMap<>(); private Project project; private String apiName; @@ -180,9 +181,7 @@ protected static CallContext register(User callingUser, Account callingAccount, } s_currentContext.set(callingContext); ThreadContext.push("ctx-" + UuidUtils.first(contextId)); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Registered: " + callingContext); - } + LOGGER.trace("Registered: {}", callingContext); s_currentContextStack.get().push(callingContext); @@ -279,9 +278,7 @@ public static CallContext unregister() { return null; } s_currentContext.remove(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Unregistered: " + context); - } + LOGGER.trace("Unregistered: {}", context); String contextId = context.getContextId(); String sessionIdOnStack = null; String sessionIdPushedToNDC = "ctx-" + UuidUtils.first(contextId); @@ -289,9 +286,7 @@ public static CallContext unregister() { if (sessionIdOnStack.isEmpty() || sessionIdPushedToNDC.equals(sessionIdOnStack)) { break; } - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Popping from NDC: " + contextId); - } + LOGGER.trace("Popping from NDC: {}", contextId); } Stack stack = s_currentContextStack.get(); @@ -394,6 +389,14 @@ public void setEventDisplayEnabled(boolean eventDisplayEnabled) { isEventDisplayEnabled = eventDisplayEnabled; } + public String getApiResourceUuid(String paramName) { + return apiResourcesUuids.get(paramName); + } + + public void putApiResourceUuid(String paramName, String uuid) { + apiResourcesUuids.put(paramName, uuid); + } + public Map getContextParameters() { return context; } diff --git a/api/src/main/java/org/apache/cloudstack/context/LogContext.java b/api/src/main/java/org/apache/cloudstack/context/LogContext.java index c367975aba3b..24b92090e7f8 100644 --- a/api/src/main/java/org/apache/cloudstack/context/LogContext.java +++ b/api/src/main/java/org/apache/cloudstack/context/LogContext.java @@ -136,9 +136,7 @@ protected static LogContext register(User callingUser, Account callingAccount, L } s_currentContext.set(callingContext); ThreadContext.put("logcontextid", UuidUtils.first(contextId)); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Registered for log: " + callingContext); - } + LOGGER.trace("Registered for log: {}", callingContext); return callingContext; } @@ -207,9 +205,7 @@ public static void unregister() { LogContext context = s_currentContext.get(); if (context != null) { s_currentContext.remove(); - if (LOGGER.isTraceEnabled()) { - LOGGER.trace("Unregistered: " + context); - } + LOGGER.trace("Unregistered: {}", context); } ThreadContext.clearMap(); } diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsProvider.java b/api/src/main/java/org/apache/cloudstack/dns/DnsProvider.java new file mode 100644 index 000000000000..231583244c2a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsProvider.java @@ -0,0 +1,45 @@ +// 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.dns; + +import java.util.List; + +import org.apache.cloudstack.dns.exception.DnsProviderException; + +import com.cloud.utils.component.Adapter; + +public interface DnsProvider extends Adapter { + DnsProviderType getProviderType(); + + // Validates connectivity to the server + void validate(DnsServer server) throws Exception; + + String validateAndResolveServer(DnsServer server) throws Exception; + + // Zone Operations + String provisionZone(DnsServer server, DnsZone zone) throws DnsProviderException; + void deleteZone(DnsServer server, DnsZone zone) throws DnsProviderException; + void updateZone(DnsServer server, DnsZone zone) throws DnsProviderException; + + String addRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException; + List listRecords(DnsServer server, DnsZone zone) throws DnsProviderException; + String updateRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException; + String deleteRecord(DnsServer server, DnsZone zone, DnsRecord record) throws DnsProviderException; + boolean dnsRecordExists(DnsServer server, DnsZone zone, String recordName, String recordType) throws DnsProviderException; + boolean dnsZoneExists(DnsServer server, DnsZone zone); +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java b/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java new file mode 100644 index 000000000000..5430884a32d9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsProviderManager.java @@ -0,0 +1,78 @@ +// 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.dns; + +import java.util.List; + +import org.apache.cloudstack.api.command.user.dns.AddDnsServerCmd; +import org.apache.cloudstack.api.command.user.dns.AssociateDnsZoneToNetworkCmd; +import org.apache.cloudstack.api.command.user.dns.CreateDnsRecordCmd; +import org.apache.cloudstack.api.command.user.dns.CreateDnsZoneCmd; +import org.apache.cloudstack.api.command.user.dns.DeleteDnsRecordCmd; +import org.apache.cloudstack.api.command.user.dns.DeleteDnsServerCmd; +import org.apache.cloudstack.api.command.user.dns.DisassociateDnsZoneFromNetworkCmd; +import org.apache.cloudstack.api.command.user.dns.ListDnsRecordsCmd; +import org.apache.cloudstack.api.command.user.dns.ListDnsServersCmd; +import org.apache.cloudstack.api.command.user.dns.ListDnsZonesCmd; +import org.apache.cloudstack.api.command.user.dns.UpdateDnsServerCmd; +import org.apache.cloudstack.api.command.user.dns.UpdateDnsZoneCmd; +import org.apache.cloudstack.api.response.DnsRecordResponse; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.ListResponse; + +import com.cloud.user.Account; +import com.cloud.utils.component.Manager; +import com.cloud.utils.component.PluggableService; + +public interface DnsProviderManager extends Manager, PluggableService { + + DnsServer addDnsServer(AddDnsServerCmd cmd); + ListResponse listDnsServers(ListDnsServersCmd cmd); + DnsServer updateDnsServer(UpdateDnsServerCmd cmd); + boolean deleteDnsServer(DeleteDnsServerCmd cmd); + DnsServerResponse createDnsServerResponse(DnsServer server); + + // Allocates the DB row (State: Inactive) + DnsZone allocateDnsZone(CreateDnsZoneCmd cmd); + // Calls the Plugin (State: Inactive -> Active) + DnsZone provisionDnsZone(long zoneId, boolean isImport); + + DnsZone updateDnsZone(UpdateDnsZoneCmd cmd); + boolean deleteDnsZone(Long id, boolean isUnmanage); + ListResponse listDnsZones(ListDnsZonesCmd cmd); + + DnsRecordResponse createDnsRecord(CreateDnsRecordCmd cmd); + boolean deleteDnsRecord(DeleteDnsRecordCmd cmd); + ListResponse listDnsRecords(ListDnsRecordsCmd cmd); + + List listProviderNames(); + + // Helper to create the response object + DnsZoneResponse createDnsZoneResponse(DnsZone zone); + DnsRecordResponse createDnsRecordResponse(DnsRecord record); + + DnsZoneNetworkMapResponse associateZoneToNetwork(AssociateDnsZoneToNetworkCmd cmd); + + boolean disassociateZoneFromNetwork(DisassociateDnsZoneFromNetworkCmd cmd); + + void checkDnsServerPermission(Account caller, DnsServer dnsServer); + + void checkDnsZonePermission(Account caller, DnsZone dnsZone); +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsProviderType.java b/api/src/main/java/org/apache/cloudstack/dns/DnsProviderType.java new file mode 100644 index 000000000000..23c8e936613f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsProviderType.java @@ -0,0 +1,23 @@ +// 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.dns; + +public enum DnsProviderType { + PowerDNS; +// Cloudflare +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsRecord.java b/api/src/main/java/org/apache/cloudstack/dns/DnsRecord.java new file mode 100644 index 000000000000..ae62e4729cca --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsRecord.java @@ -0,0 +1,66 @@ +// 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.dns; + +import java.util.List; + +import com.cloud.utils.exception.CloudRuntimeException; + +public class DnsRecord { + + public enum RecordType { + A, AAAA, CNAME, MX, TXT, SRV, PTR, NS; + + public static RecordType fromString(String type) { + if (type == null) return null; + try { + return RecordType.valueOf(type.toUpperCase()); + } catch (IllegalArgumentException e) { + throw new CloudRuntimeException("Invalid DNS Record Type: " + type + + ". Supported: " + java.util.Arrays.toString(values())); + } + } + } + + private String name; + private RecordType type; + private List contents; + private int ttl; + + public DnsRecord() {} + + public DnsRecord(String name, RecordType type, List contents, int ttl) { + this.name = name; + this.type = type; + this.contents = contents; + this.ttl = ttl; + } + + // Getters and Setters + public String getName() { return name; } + public void setName(String name) { this.name = name; } + + public RecordType getType() { return type; } + public void setType(RecordType type) { this.type = type; } + + public List getContents() { return contents; } + public void setContents(List contents) { this.contents = contents; } + + public int getTtl() { return ttl; } + public void setTtl(int ttl) { this.ttl = ttl; } +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsServer.java b/api/src/main/java/org/apache/cloudstack/dns/DnsServer.java new file mode 100644 index 000000000000..f34f94726d1b --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsServer.java @@ -0,0 +1,62 @@ +// 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.dns; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface DnsServer extends InternalIdentity, Identity, ControlledEntity { + enum State { + Enabled, Disabled + }; + + String getName(); + + String getUrl(); + + DnsProviderType getProviderType(); + + List getNameServers(); + + String getDnsApiKey(); + + long getAccountId(); + + boolean getPublicServer(); + + Date getCreated(); + + Date getRemoved(); + + String getPublicDomainSuffix(); + + Integer getPort(); + + Map getDetails(); + + String getDetail(String name); + + void setDetails(Map details); + + void appendDetails(String name, String value); +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/DnsZone.java b/api/src/main/java/org/apache/cloudstack/dns/DnsZone.java new file mode 100644 index 000000000000..193185a6b0c6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/DnsZone.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns; + +import java.util.List; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface DnsZone extends InternalIdentity, Identity, ControlledEntity { + enum ZoneType { + Public, Private + } + enum State { + Active, Inactive + } + + String getName(); + + long getDnsServerId(); + + long getAccountId(); + + ZoneType getType(); + + String getDescription(); + + List getAssociatedNetworks(); + + State getState(); +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/exception/DnsAuthenticationException.java b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsAuthenticationException.java new file mode 100644 index 000000000000..325cb78241ef --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsAuthenticationException.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns.exception; + +/** + * Thrown when authentication to the DNS provider fails. + */ +public class DnsAuthenticationException extends DnsProviderException { + public DnsAuthenticationException(String message) { + super(message); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/exception/DnsConflictException.java b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsConflictException.java new file mode 100644 index 000000000000..9a36bb87478a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsConflictException.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns.exception; + +/** + * Thrown when attempting to create a zone or record that already exists. + */ +public class DnsConflictException extends DnsProviderException { + public DnsConflictException(String message) { + super(message); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/exception/DnsNotFoundException.java b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsNotFoundException.java new file mode 100644 index 000000000000..aa88f308ce84 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsNotFoundException.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns.exception; + +/** + * Thrown when the requested zone or record does not exist. + */ +public class DnsNotFoundException extends DnsProviderException { + public DnsNotFoundException(String message) { + super(message); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/exception/DnsOperationException.java b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsOperationException.java new file mode 100644 index 000000000000..564acdc9a6f0 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsOperationException.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns.exception; + +/** + * Thrown for unexpected or unknown errors returned by the DNS provider. + */ +public class DnsOperationException extends DnsProviderException { + public DnsOperationException(String message) { + super(message); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/exception/DnsProviderException.java b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsProviderException.java new file mode 100644 index 000000000000..de307c9903e4 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsProviderException.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns.exception; + +public class DnsProviderException extends Exception { + public DnsProviderException(String message) { + super(message); + } + + public DnsProviderException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/dns/exception/DnsTransportException.java b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsTransportException.java new file mode 100644 index 000000000000..50f04143c921 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/dns/exception/DnsTransportException.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.dns.exception; + +import java.io.IOException; + +/** + * Thrown when HTTP or network errors occur communicating with the DNS provider. + */ +public class DnsTransportException extends DnsProviderException { + + public DnsTransportException(String message, IOException cause) { + super(message, cause); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/extension/CustomActionResultResponse.java b/api/src/main/java/org/apache/cloudstack/extension/CustomActionResultResponse.java new file mode 100644 index 000000000000..558340694b8a --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/extension/CustomActionResultResponse.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.extension; + +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.BaseResponse; + +import com.cloud.serializer.Param; +import com.google.gson.annotations.SerializedName; + +public class CustomActionResultResponse extends BaseResponse { + + @SerializedName(ApiConstants.ID) + @Param(description = "ID of the action") + private String id; + + @SerializedName(ApiConstants.NAME) + @Param(description = "Name of the action") + private String name; + + @SerializedName(ApiConstants.SUCCESS) + @Param(description = "Whether custom action succeed or not") + private Boolean success; + + @SerializedName(ApiConstants.RESULT) + @Param(description = "Result of the action execution") + private Map result; + + public void setId(String id) { + this.id = id; + } + + public void setName(String name) { + this.name = name; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public Boolean getSuccess() { + return success; + } + + public void setResult(Map result) { + this.result = result; + } + + public Map getResult() { + return result; + } + + public boolean isSuccess() { + return Boolean.TRUE.equals(success); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/extension/Extension.java b/api/src/main/java/org/apache/cloudstack/extension/Extension.java new file mode 100644 index 000000000000..c1d905718b24 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/extension/Extension.java @@ -0,0 +1,45 @@ +//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.extension; + +import java.util.Date; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface Extension extends InternalIdentity, Identity { + enum Type { + Orchestrator, + NetworkOrchestrator + } + enum State { + Enabled, Disabled; + }; + String getName(); + String getDescription(); + Type getType(); + String getRelativePath(); + boolean isPathReady(); + boolean isUserDefined(); + State getState(); + Date getCreated(); + + static String getDirectoryName(String name) { + return name.replaceAll("[^a-zA-Z0-9._-]", "_").toLowerCase(); + } +} diff --git a/api/src/main/java/org/apache/cloudstack/extension/ExtensionCustomAction.java b/api/src/main/java/org/apache/cloudstack/extension/ExtensionCustomAction.java new file mode 100644 index 000000000000..245faa762a2f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/extension/ExtensionCustomAction.java @@ -0,0 +1,388 @@ +//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.extension; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.EnumUtils; +import org.apache.commons.lang3.StringUtils; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.DateUtil; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParseException; +import com.google.gson.reflect.TypeToken; + +public interface ExtensionCustomAction extends InternalIdentity, Identity { + enum ResourceType { + VirtualMachine(com.cloud.vm.VirtualMachine.class), + Network(com.cloud.network.Network.class), + Vpc(com.cloud.network.vpc.Vpc.class); + + private final Class clazz; + + ResourceType(Class clazz) { + this.clazz = clazz; + } + + public Class getAssociatedClass() { + return this.clazz; + } + } + + String getName(); + + String getDescription(); + + long getExtensionId(); + + ResourceType getResourceType(); + + Integer getAllowedRoleTypes(); + + String getSuccessMessage(); + + String getErrorMessage(); + + int getTimeout(); + + boolean isEnabled(); + + Date getCreated(); + + + class Parameter { + + public enum Type { + STRING(true), + NUMBER(true), + BOOLEAN(false), + DATE(false); + + private final boolean supportsOptions; + + Type(boolean supportsOptions) { + this.supportsOptions = supportsOptions; + } + + public boolean canSupportsOptions() { + return supportsOptions; + } + } + + public enum ValidationFormat { + // Universal default format + NONE(null), + + // String formats + UUID(Type.STRING), + EMAIL(Type.STRING), + PASSWORD(Type.STRING), + URL(Type.STRING), + + // Number formats + DECIMAL(Type.NUMBER); + + private final Type baseType; + + ValidationFormat(Type baseType) { + this.baseType = baseType; + } + + public Type getBaseType() { + return baseType; + } + } + + private static final Gson gson = new GsonBuilder() + .registerTypeAdapter(Parameter.class, new ParameterDeserializer()) + .setPrettyPrinting() + .create(); + + private final String name; + private final Type type; + private final ValidationFormat validationformat; + private final List valueoptions; + private final boolean required; + + public Parameter(String name, Type type, ValidationFormat validationformat, List valueoptions, boolean required) { + this.name = name; + this.type = type; + this.validationformat = validationformat; + this.valueoptions = valueoptions; + this.required = required; + } + + /** + * Parses a CSV string into a list of validated options. + */ + private static List parseValueOptions(String name, String csv, Type parsedType, ValidationFormat parsedFormat) { + if (StringUtils.isBlank(csv)) { + return null; + } + List values = Arrays.stream(csv.split(",")) + .map(String::trim) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + switch (parsedType) { + case STRING: + if (parsedFormat != null && parsedFormat != ValidationFormat.NONE) { + for (String value : values) { + if (!isValidStringValue(value, parsedFormat)) { + throw new InvalidParameterValueException(String.format("Invalid value options with validation format: %s for parameter: %s", parsedFormat.name(), name)); + } + } + } + return new ArrayList<>(values); + case NUMBER: + try { + return values.stream() + .map(v -> parseNumber(v, parsedFormat)) + .collect(Collectors.toList()); + } catch (NumberFormatException ignored) { + throw new InvalidParameterValueException(String.format("Invalid value options with validation format: %s for parameter: %s", parsedFormat.name(), name)); + } + default: + throw new InvalidParameterValueException(String.format("Options not supported for type: %s for parameter: %s", parsedType, name)); + } + } + + private static Object parseNumber(String value, ValidationFormat parsedFormat) { + if (parsedFormat == ValidationFormat.DECIMAL) { + return Float.parseFloat(value); + } + return Integer.parseInt(value); + } + + private static boolean isValidStringValue(String value, ValidationFormat validationFormat ) { + switch (validationFormat) { + case NONE: + return true; + case UUID: + try { + UUID.fromString(value); + return true; + } catch (Exception ignored) { + return false; + } + case EMAIL: + return value.matches("^[A-Za-z0-9+_.-]+@[A-Za-z0-9.-]+$"); + case PASSWORD: + return !value.trim().isEmpty(); + case URL: + try { + new java.net.URL(value); + return true; + } catch (Exception ignored) { + return false; + } + default: + return false; + } + } + + public static Parameter fromMap(Map map) throws InvalidParameterValueException { + final String name = map.get(ApiConstants.NAME); + final String typeStr = map.get(ApiConstants.TYPE); + final String validationFormatStr = map.get(ApiConstants.VALIDATION_FORMAT); + final String required = map.get(ApiConstants.REQUIRED); + final String valueOptionsStr = map.get(ApiConstants.VALUE_OPTIONS); + if (StringUtils.isBlank(name)) { + throw new InvalidParameterValueException("Invalid parameter specified with empty name"); + } + if (StringUtils.isBlank(typeStr)) { + throw new InvalidParameterValueException(String.format("No type specified for parameter: %s", name)); + } + Type parsedType = EnumUtils.getEnumIgnoreCase(Type.class, typeStr); + if (parsedType == null) { + throw new InvalidParameterValueException(String.format("Invalid type: %s specified for parameter: %s", + typeStr, name)); + } + ValidationFormat parsedFormat = StringUtils.isBlank(validationFormatStr) ? + ValidationFormat.NONE : EnumUtils.getEnumIgnoreCase(ValidationFormat.class, validationFormatStr); + if (parsedFormat == null || (!ValidationFormat.NONE.equals(parsedFormat) && + parsedFormat.getBaseType() != parsedType)) { + throw new InvalidParameterValueException( + String.format("Invalid validation format: %s specified for type: %s", + parsedFormat, parsedType.name())); + } + List valueOptions = parseValueOptions(name, valueOptionsStr, parsedType, parsedFormat); + return new Parameter(name, parsedType, parsedFormat, valueOptions, Boolean.parseBoolean(required)); + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public ValidationFormat getValidationFormat() { + return validationformat; + } + + public List getValueOptions() { + return valueoptions; + } + + public boolean isRequired() { + return required; + } + + @Override + public String toString() { + return String.format("Parameter %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, + ApiConstants.NAME, ApiConstants.TYPE, ApiConstants.REQUIRED)); + } + + public static String toJsonFromList(List parameters) { + return gson.toJson(parameters); + } + + public static List toListFromJson(String json) { + java.lang.reflect.Type listType = new TypeToken>() {}.getType(); + return gson.fromJson(json, listType); + } + + private void validateValueInOptions(Object value) { + if (CollectionUtils.isNotEmpty(valueoptions) && !valueoptions.contains(value)) { + throw new InvalidParameterValueException("Invalid value for parameter '" + name + "': " + value + + ". Valid options are: " + valueoptions.stream().map(Object::toString).collect(Collectors.joining(", "))); + } + } + + public Object validatedValue(String value) { + if (StringUtils.isBlank(value)) { + throw new InvalidParameterValueException("Empty value for parameter '" + name + "': " + value); + } + try { + switch (type) { + case BOOLEAN: + if (!Arrays.asList("true", "false").contains(value)) { + throw new IllegalArgumentException(); + } + return Boolean.parseBoolean(value); + case DATE: + return DateUtil.parseTZDateString(value); + case NUMBER: + Object obj = parseNumber(value, validationformat); + validateValueInOptions(obj); + return obj; + default: + if (!isValidStringValue(value, validationformat)) { + throw new IllegalArgumentException(); + } + validateValueInOptions(value); + return value; + } + } catch (Exception e) { + throw new InvalidParameterValueException("Invalid value for parameter '" + name + "': " + value); + } + } + + public static Map validateParameterValues(List parameterDefinitions, + Map suppliedValues) throws InvalidParameterValueException { + if (suppliedValues == null) { + suppliedValues = new HashMap<>(); + } + for (Parameter param : parameterDefinitions) { + String value = suppliedValues.get(param.getName()); + if (param.isRequired()) { + if (value == null || value.trim().isEmpty()) { + throw new InvalidParameterValueException("Missing or empty required parameter: " + param.getName()); + } + } + } + Map validatedParams = new HashMap<>(); + for (Map.Entry entry : suppliedValues.entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + Parameter param = parameterDefinitions.stream() + .filter(p -> p.getName().equals(name)) + .findFirst() + .orElse(null); + if (param != null) { + validatedParams.put(name, param.validatedValue(value)); + } else { + validatedParams.put(name, value); + } + } + return validatedParams; + } + + static class ParameterDeserializer implements JsonDeserializer { + + @Override + public Parameter deserialize(JsonElement json, java.lang.reflect.Type typeOfT, JsonDeserializationContext context) throws JsonParseException { + JsonObject obj = json.getAsJsonObject(); + String name = obj.get(ApiConstants.NAME).getAsString(); + String typeStr = obj.get(ApiConstants.TYPE).getAsString(); + String validationFormatStr = obj.has(ApiConstants.VALIDATION_FORMAT) ? obj.get(ApiConstants.VALIDATION_FORMAT).getAsString() : null; + boolean required = obj.has(ApiConstants.REQUIRED) && obj.get(ApiConstants.REQUIRED).getAsBoolean(); + + Parameter.Type typeEnum = Parameter.Type.valueOf(typeStr); + Parameter.ValidationFormat validationFormatEnum = (validationFormatStr != null) + ? Parameter.ValidationFormat.valueOf(validationFormatStr) + : Parameter.ValidationFormat.NONE; + + List valueOptions = null; + if (obj.has(ApiConstants.VALUE_OPTIONS) && obj.get(ApiConstants.VALUE_OPTIONS).isJsonArray()) { + JsonArray valueOptionsArray = obj.getAsJsonArray(ApiConstants.VALUE_OPTIONS); + valueOptions = new ArrayList<>(); + for (JsonElement el : valueOptionsArray) { + switch (typeEnum) { + case STRING: + valueOptions.add(el.getAsString()); + break; + case NUMBER: + if (validationFormatEnum == Parameter.ValidationFormat.DECIMAL) { + valueOptions.add(el.getAsFloat()); + } else { + valueOptions.add(el.getAsInt()); + } + break; + default: + throw new JsonParseException("Unsupported type for value options"); + } + } + } + + return new Parameter(name, typeEnum, validationFormatEnum, valueOptions, required); + } + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java b/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java new file mode 100644 index 000000000000..e3d84745f997 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/extension/ExtensionHelper.java @@ -0,0 +1,142 @@ +// 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.extension; + +import java.util.List; +import java.util.Map; + +import com.cloud.network.Network.Capability; +import com.cloud.network.Network.Service; + +public interface ExtensionHelper { + Long getExtensionIdForCluster(long clusterId); + Extension getExtension(long id); + Extension getExtensionByNameAndType(String name, Extension.Type type); + Extension getExtensionForCluster(long clusterId); + List getExtensionReservedResourceDetails(long extensionId); + + /** + * Network Guru of Network Extension + */ + String NETWORK_EXTENSION_GURU_NAME = "NetworkExtensionGuestNetworkGuru"; + + /** + * Extension resource-map detail key holding the isolation method for the physical + * network (e.g. {@code "NetworkExtension"}). + * If not specified, the isolation method of physical network will be used. + */ + String NETWORK_ISOLATION_METHOD_DETAIL_KEY = "network.isolation.method"; + + /** + * Value of {@link #NETWORK_ISOLATION_METHOD_DETAIL_KEY} that indicates + * the extension owns the network isolation. + */ + String NETWORK_EXTENSION_ISOLATION_METHOD = "NetworkExtension"; + + /** + * Detail key used to store the comma-separated list of network services provided + * by a NetworkOrchestrator extension (e.g. {@code "SourceNat,StaticNat,Firewall"}). + */ + String NETWORK_SERVICES_DETAIL_KEY = "network.services"; + + /** + * Detail key used to store a JSON object mapping each service name to its + * CloudStack {@link com.cloud.network.Network.Capability} key/value pairs. + * Example: {@code {"SourceNat":{"SupportedSourceNatTypes":"peraccount"}}}. + * Used together with {@link #NETWORK_SERVICES_DETAIL_KEY}. + */ + String NETWORK_SERVICE_CAPABILITIES_DETAIL_KEY = "network.service.capabilities"; + + String getExtensionScriptPath(Extension extension); + + /** + * Finds the extension registered with the given physical network whose name + * matches the given provider name (case-insensitive). Returns {@code null} + * if no matching extension is found. + * + *

This is the preferred lookup when multiple extensions are registered on + * the same physical network: the provider name stored in + * {@code ntwk_service_map} is used to pinpoint the exact extension that + * handles a given network.

+ * + * @param physicalNetworkId the physical network ID + * @param providerName the provider name (must equal the extension name) + * @return the matching {@link Extension}, or {@code null} + */ + Extension getExtensionForPhysicalNetworkAndProvider(long physicalNetworkId, String providerName); + + /** + * Returns ALL {@code extension_resource_map_details} (including hidden) for + * the specific extension registered on the given physical network. Used by + * {@code NetworkExtensionElement} to inject device credentials into the script + * environment for the correct extension when multiple different extensions are + * registered on the same physical network. + * + * @param physicalNetworkId the physical network ID + * @param extensionId the extension ID + * @return all key/value details including non-display ones, or an empty map + */ + Map getAllResourceMapDetailsForExtensionOnPhysicalNetwork(long physicalNetworkId, long extensionId); + + /** + * Returns {@code true} if the given provider name is backed by a + * {@code NetworkOrchestrator} extension registered on any physical network. + * This is used by {@code NetworkModelImpl} to detect extension-backed providers + * that are not in the static {@code s_providerToNetworkElementMap}. + * + * @param providerName the provider / extension name + * @return true if the provider is a NetworkExtension provider + */ + boolean isNetworkExtensionProvider(String providerName); + + /** + * List all registered extensions filtered by extension {@link Extension.Type}. + * Useful for callers that need to discover available providers of a given + * type (e.g. Orchestrator, NetworkOrchestrator). + * + * @param type extension type to filter by + * @return list of matching {@link Extension} instances (empty list if none) + */ + List listExtensionsByType(Extension.Type type); + + /** + * Returns {@code true} when the extension registered for {@code providerName} on + * the given physical network has its {@link #NETWORK_ISOLATION_METHOD_DETAIL_KEY} + * resource-map detail set to {@link #NETWORK_EXTENSION_ISOLATION_METHOD}. + * + * @param providerName provider / extension name + * @return true if the extension uses NetworkExtension isolation + */ + boolean usesNetworkExtensionIsolation(String providerName); + + /** + * Returns the effective {@link Service} → ({@link Capability} → value) capabilities + * for the given external network provider, looking it up by name on the given + * physical network. + * + *

If {@code physicalNetworkId} is {@code null}, the method searches across all + * physical networks that have extensions registered and returns the capabilities for + * the first matching extension.

+ * + * @param physicalNetworkId physical network ID, or {@code null} for offering-level queries + * @param providerName provider / extension name + * @return capabilities map, or the default capabilities if no matching extension is found + */ + Map> getNetworkCapabilitiesForProvider(Long physicalNetworkId, String providerName); + +} diff --git a/api/src/main/java/org/apache/cloudstack/extension/ExtensionResourceMap.java b/api/src/main/java/org/apache/cloudstack/extension/ExtensionResourceMap.java new file mode 100644 index 000000000000..604e64a1f894 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/extension/ExtensionResourceMap.java @@ -0,0 +1,35 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.extension; + +import java.util.Date; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface ExtensionResourceMap extends InternalIdentity, Identity { + enum ResourceType { + Cluster, + PhysicalNetwork + } + + long getExtensionId(); + long getResourceId(); + ResourceType getResourceType(); + Date getCreated(); +} diff --git a/api/src/main/java/org/apache/cloudstack/extension/NetworkCustomActionProvider.java b/api/src/main/java/org/apache/cloudstack/extension/NetworkCustomActionProvider.java new file mode 100644 index 000000000000..1c281c15f286 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/extension/NetworkCustomActionProvider.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.extension; + +import java.util.Map; + +import com.cloud.network.Network; +import com.cloud.network.vpc.Vpc; + +/** + * Implemented by network elements that support running custom actions on a + * managed network or VPC (e.g. NetworkExtensionElement). + * + *

This interface is looked up by {@code ExtensionsManagerImpl} to dispatch + * {@code runCustomAction} requests whose resource type is {@code Network} + * or {@code Vpc}.

+ */ +public interface NetworkCustomActionProvider { + + /** + * Returns {@code true} if this provider handles networks whose physical + * network has an ExternalNetwork service provider registered. + * + * @param network the target network + * @return {@code true} if this provider can handle the network + */ + boolean canHandleCustomAction(Network network); + + /** + * Returns {@code true} if this provider can handle custom actions for + * the given VPC. + * + * @param vpc the target VPC + * @return {@code true} if this provider can handle the VPC + */ + boolean canHandleVpcCustomAction(Vpc vpc); + + /** + * Runs a named custom action against the external network device that + * manages the given network. + * + * @param network the CloudStack network on which to run the action + * @param actionName the action name (e.g. {@code "reboot-device"}, {@code "dump-config"}) + * @param parameters optional parameters supplied by the caller + * @return output from the action script, or {@code null} on failure + */ + String runCustomAction(Network network, String actionName, Map parameters); + + /** + * Runs a named custom action against the external network device that + * manages the given VPC. + * + * @param vpc the CloudStack VPC on which to run the action + * @param actionName the action name + * @param parameters optional parameters supplied by the caller + * @return output from the action script, or {@code null} on failure + */ + String runCustomAction(Vpc vpc, String actionName, Map parameters); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java new file mode 100644 index 000000000000..2c02a0e30c26 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuCard.java @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gpu; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +/** + * GPU card interface representing a physical GPU card model + */ +public interface GpuCard extends InternalIdentity, Identity { + /** + * @return the UUID of the GPU card + */ + String getUuid(); + + /** + * @return the device ID of the GPU card + */ + String getDeviceId(); + + /** + * @return the device name of the GPU card + */ + String getDeviceName(); + + /** + * @return the name of the GPU card + */ + String getName(); + + /** + * @return the vendor name of the GPU card + */ + String getVendorName(); + + /** + * @return the vendor ID of the GPU card + */ + String getVendorId(); + + /** + * @return the date when the GPU card was created + */ + Date getCreated(); + + + /** + * @return the group name of the GPU card based on how the XenServer expects it. + */ + String getGroupName(); + +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java new file mode 100644 index 000000000000..22adda464779 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuDevice.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gpu; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +/** + * GPU device interface representing a physical GPU device + */ +public interface GpuDevice extends InternalIdentity, Identity { + + enum State { + Allocated, Free, Error, PartiallyAllocated, + } + + enum ManagedState { + Managed, Unmanaged, + } + + enum DeviceType { + PCI, MDEV, VGPUOnly, + } + + long getHostId(); + + State getState(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/GpuService.java b/api/src/main/java/org/apache/cloudstack/gpu/GpuService.java new file mode 100644 index 000000000000..1e7928a319f6 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/GpuService.java @@ -0,0 +1,157 @@ +// 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.gpu; + +import com.cloud.agent.api.VgpuTypesInfo; +import com.cloud.agent.api.to.GPUDeviceTO; +import com.cloud.host.Host; +import com.cloud.utils.component.Manager; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.api.command.admin.gpu.CreateGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.CreateVgpuProfileCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.DeleteVgpuProfileCmd; +import org.apache.cloudstack.api.command.admin.gpu.UnmanageGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.DiscoverGpuDevicesCmd; +import org.apache.cloudstack.api.command.admin.gpu.ManageGpuDeviceCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuDevicesCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateGpuCardCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateGpuDeviceCmd; +import org.apache.cloudstack.api.command.admin.gpu.UpdateVgpuProfileCmd; +import org.apache.cloudstack.api.command.user.gpu.ListGpuCardsCmd; +import org.apache.cloudstack.api.command.user.gpu.ListVgpuProfilesCmd; +import org.apache.cloudstack.api.response.GpuCardResponse; +import org.apache.cloudstack.api.response.GpuDeviceResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.VgpuProfileResponse; +import org.apache.cloudstack.framework.config.ConfigKey; + +import java.util.HashMap; +import java.util.List; + +public interface GpuService extends Manager { + + ConfigKey GpuDetachOnStop = new ConfigKey<>(Boolean.class, "gpu.detach.on.stop", "Advanced", "false", + "Whether to detach GPU devices from VM on stop or keep them allocated", true, ConfigKey.Scope.Domain, null); + + GpuCard createGpuCard(CreateGpuCardCmd cmd); + + GpuCard updateGpuCard(UpdateGpuCardCmd cmd); + + boolean deleteGpuCard(DeleteGpuCardCmd cmd); + + VgpuProfileResponse createVgpuProfile(CreateVgpuProfileCmd cmd); + + VgpuProfileResponse updateVgpuProfile(UpdateVgpuProfileCmd cmd); + + boolean deleteVgpuProfile(DeleteVgpuProfileCmd cmd); + + ListResponse listGpuCards(ListGpuCardsCmd cmd); + + ListResponse listVgpuProfiles(ListVgpuProfilesCmd cmd); + + GpuDeviceResponse createGpuDevice(CreateGpuDeviceCmd cmd); + + GpuDeviceResponse updateGpuDevice(UpdateGpuDeviceCmd cmd); + + ListResponse listGpuDevices(ListGpuDevicesCmd cmd); + + boolean disableGpuDevice(UnmanageGpuDeviceCmd cmd); + + boolean enableGpuDevice(ManageGpuDeviceCmd cmd); + + /** + * Deallocate GPU devices for a VM on a host. + * + * @param vmId The ID of the VM to deallocate GPU devices for. + */ + void deallocateAllGpuDevicesForVm(long vmId); + + + /** + * Deallocate GPU devices for a VM on a host. + * + * @param vmId The ID of the VM to deallocate GPU devices for. + */ + void deallocateGpuDevicesForVmOnHost(long vmId, long hostId); + + /** + * Deallocate existing GPU devices for a VM on a host and allocate new GPU devices to the VM. + * + * @param vmId The ID of the VM to allocate GPU devices to. + * @param hostId The ID of the host to allocate GPU devices to. + * @param gpuDevices The list of GPU devices to allocate to the VM. + */ + void allocateGpuDevicesToVmOnHost(long vmId, long hostId, List gpuDevices); + + /** + * Discover GPU devices on a host by using the getGPUStatistics command and updating the GPU details for the host. + * + * @param cmd The command to discover GPU devices. + * @return The list of GPU devices. + */ + ListResponse discoverGpuDevices(DiscoverGpuDevicesCmd cmd); + + /** + * Check if GPU devices are available for a VM on a host by checking the number of available GPU devices for the + * vGPU profile. + * + * @param host The host to check GPU devices for. + * @param vmId The ID of the VM to check GPU devices for. + * @param vgpuProfile The vGPU profile to check GPU devices for. + * @param gpuCount The number of GPU devices to check for. + * @return True if GPU devices are available, false otherwise. + */ + boolean isGPUDeviceAvailable(Host host, Long vmId, VgpuProfile vgpuProfile, int gpuCount); + + /** + * Get GPU devices for a VM on a host by checking the number of available GPU devices for the vGPU profile. + * If the VM already has GPU devices assigned, deallocate them and allocate new GPU devices to the VM. + * The new GPU devices are allocated optimally to the VM. + * + * @param vm The VM to get GPU devices for. + * @param vgpuProfile The vGPU profile to get GPU devices for. + * @param gpuCount The number of GPU devices to get. + * @return The GPU devices. + */ + GPUDeviceTO getGPUDevice(VirtualMachine vm, long hostId, VgpuProfile vgpuProfile, int gpuCount); + + /** + * Gets the GPU group details from the GPU devices on a host. + * This fetches the GPU devices from the host and prepares the GPU group details for the host. + * The GPU group details are a map of GPU group name (Card's device name) to a map of vGPU profile name to + * VgpuTypesInfo. + * The VgpuTypesInfo contains the information about the GPU device. + * + * @param hostId The host ID to get GPU group details for. + * @return The GPU group details. + */ + HashMap> getGpuGroupDetailsFromGpuDevicesOnHost(long hostId); + + /** + * This method is used to add the GPU devices to the host when the host is discovered or when the GPU devices are + * updated. + * + * @param host The host to add the GPU devices to. + * @param newGpuDevicesInfo The list of GPU devices to add to the host. + */ + void addGpuDevicesToHost(Host host, List newGpuDevicesInfo); + + boolean deleteGpuDevices(DeleteGpuDeviceCmd deleteGpuDeviceCmd); +} diff --git a/api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java b/api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java new file mode 100644 index 000000000000..8cfac2a20de8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gpu/VgpuProfile.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gpu; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +/** + * vGPU profile interface representing a virtualized GPU profile + */ +public interface VgpuProfile extends InternalIdentity, Identity { + /** + * @return the UUID of the vGPU profile + */ + String getUuid(); + + /** + * @return the name of the vGPU profile + */ + String getName(); + + /** + * @return the description of the vGPU profile + */ + String getDescription(); + + /** + * @return the date when the vGPU profile was created + */ + Date getCreated(); + + Long getCardId(); + + /** + * @return the maximum number of vGPUs per physical GPU + */ + Long getMaxVgpuPerPgpu(); + + /** + * @return the video RAM size in MB + */ + Long getVideoRam(); + + /** + * @return the maximum number of display heads + */ + Long getMaxHeads(); + + /** + * @return the maximum X resolution + */ + Long getMaxResolutionX(); + + /** + * @return the maximum Y resolution + */ + Long getMaxResolutionY(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java new file mode 100644 index 000000000000..0cca8bc2d7db --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiTheme.java @@ -0,0 +1,61 @@ +// 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.gui.theme; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface GuiTheme extends InternalIdentity, Identity { + + String getName(); + + String getDescription(); + + String getCss(); + + String getJsonConfiguration(); + + Date getCreated(); + + Date getRemoved(); + + boolean getIsPublic(); + + void setId(Long id); + + void setUuid(String uuid); + + void setName(String name); + + void setDescription(String description); + + void setCss(String css); + + void setJsonConfiguration(String jsonConfiguration); + + void setCreated(Date created); + + void setRemoved(Date removed); + + void setIsPublic(boolean isPublic); + + boolean isRecursiveDomains(); + + void setRecursiveDomains(boolean recursiveDomains); +} diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetails.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetails.java new file mode 100644 index 000000000000..164535033d98 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetails.java @@ -0,0 +1,36 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme; + +import org.apache.cloudstack.api.InternalIdentity; + +public interface GuiThemeDetails extends InternalIdentity { + + void setId(Long id); + + Long getGuiThemeId(); + + void setGuiThemeId(Long guiThemeId); + + String getType(); + + void setType(String type); + + String getValue(); + + void setValue(String value); +} diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java new file mode 100644 index 000000000000..cb3708cda373 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoin.java @@ -0,0 +1,49 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface GuiThemeJoin extends InternalIdentity, Identity { + + String getName(); + + String getDescription(); + + String getCss(); + + String getJsonConfiguration(); + + String getCommonNames(); + + String getDomains(); + + String getAccounts(); + + boolean isRecursiveDomains(); + + boolean getIsPublic(); + + Date getCreated(); + + Date getRemoved(); + + String getLoginBaseDomain(); +} diff --git a/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeService.java b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeService.java new file mode 100644 index 000000000000..8a066d3ffeca --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeService.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme; + +import org.apache.cloudstack.api.command.user.gui.theme.CreateGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.ListGuiThemesCmd; +import org.apache.cloudstack.api.command.user.gui.theme.RemoveGuiThemeCmd; +import org.apache.cloudstack.api.command.user.gui.theme.UpdateGuiThemeCmd; +import org.apache.cloudstack.api.response.GuiThemeResponse; +import org.apache.cloudstack.api.response.ListResponse; + +public interface GuiThemeService { + + ListResponse listGuiThemes(ListGuiThemesCmd cmd); + + GuiThemeJoin createGuiTheme(CreateGuiThemeCmd cmd); + + GuiThemeJoin updateGuiTheme(UpdateGuiThemeCmd cmd); + + void removeGuiTheme(RemoveGuiThemeCmd cmd); +} diff --git a/api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java b/api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java new file mode 100644 index 000000000000..bbb8617ff24f --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/kms/HSMProfile.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +import java.util.Date; + +public interface HSMProfile extends Identity, InternalIdentity, ControlledEntity { + String getName(); + + String getProtocol(); + + long getAccountId(); + + long getDomainId(); + + Long getZoneId(); + + String getVendorName(); + + boolean isEnabled(); + + boolean getIsPublic(); + + Date getCreated(); + + Date getRemoved(); +} diff --git a/api/src/main/java/org/apache/cloudstack/kms/KMSKey.java b/api/src/main/java/org/apache/cloudstack/kms/KMSKey.java new file mode 100644 index 000000000000..c956a1ec66d8 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/kms/KMSKey.java @@ -0,0 +1,60 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.kms; + +import org.apache.cloudstack.acl.ControlledEntity; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.framework.kms.KeyPurpose; + +import java.util.Date; + +/** + * KMS Key (Key Encryption Key) metadata. + * Represents a KEK that can be used to wrap/unwrap Data Encryption Keys (DEKs). + * KEKs are account-scoped and used for envelope encryption. + */ +public interface KMSKey extends Identity, InternalIdentity, ControlledEntity { + + String getName(); + + String getDescription(); + + /** + * Provider-specific KEK label/ID (internal identifier used by the KMS provider) + */ + String getKekLabel(); + + KeyPurpose getPurpose(); + + Long getZoneId(); + + String getAlgorithm(); + + Integer getKeyBits(); + + boolean isEnabled(); + + Date getCreated(); + + Date getRemoved(); + + Long getHsmProfileId(); +} diff --git a/api/src/main/java/org/apache/cloudstack/kms/KMSManager.java b/api/src/main/java/org/apache/cloudstack/kms/KMSManager.java new file mode 100644 index 000000000000..6e19010227d7 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/kms/KMSManager.java @@ -0,0 +1,292 @@ +// 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.kms; + +import com.cloud.storage.Volume; +import com.cloud.user.Account; +import com.cloud.utils.component.Manager; +import org.apache.cloudstack.api.command.admin.kms.MigrateVolumesToKMSCmd; +import org.apache.cloudstack.api.command.user.kms.RotateKMSKeyCmd; +import org.apache.cloudstack.api.command.user.kms.CreateKMSKeyCmd; +import org.apache.cloudstack.api.command.user.kms.DeleteKMSKeyCmd; +import org.apache.cloudstack.api.command.user.kms.ListKMSKeysCmd; +import org.apache.cloudstack.api.command.user.kms.UpdateKMSKeyCmd; +import org.apache.cloudstack.api.command.user.kms.hsm.CreateHSMProfileCmd; +import org.apache.cloudstack.api.command.user.kms.hsm.DeleteHSMProfileCmd; +import org.apache.cloudstack.api.command.user.kms.hsm.ListHSMProfilesCmd; +import org.apache.cloudstack.api.command.user.kms.hsm.UpdateHSMProfileCmd; +import org.apache.cloudstack.api.response.HSMProfileResponse; +import org.apache.cloudstack.api.response.KMSKeyResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.framework.kms.KMSProvider; +import org.apache.cloudstack.framework.kms.WrappedKey; + +import java.util.List; + +public interface KMSManager extends Manager, Configurable { + + ConfigKey KMSDekSizeBits = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.dek.size.bits", + "256", + "The size of Data Encryption Keys (DEK) in bits (128, 192, or 256)", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSRetryCount = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.retry.count", + "3", + "Number of retry attempts for transient KMS failures", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSRetryDelayMs = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.retry.delay.ms", + "1000", + "Delay in milliseconds between KMS retry attempts (exponential backoff)", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSOperationTimeoutSec = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.operation.timeout.sec", + "30", + "Timeout in seconds for KMS cryptographic operations", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSRewrapBatchSize = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.rewrap.batch.size", + "50", + "Number of wrapped keys to rewrap per batch in background job", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSRewrapIntervalMs = new ConfigKey<>( + "Advanced", + Long.class, + "kms.rewrap.interval.ms", + "300000", + "Interval in milliseconds between background rewrap job executions (default: 5 minutes)", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSOperationPoolCoreSize = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.operation.pool.core.size", + "2", + "Minimum number of threads kept alive for KMS cryptographic operations", + true, + ConfigKey.Scope.Global + ); + + ConfigKey KMSOperationPoolMaxSize = new ConfigKey<>( + "Advanced", + Integer.class, + "kms.operation.pool.max.size", + "100", + "Maximum number of concurrent threads for KMS cryptographic operations. " + + "Set this to match the concurrency limit of your HSM appliance or external KMS provider.", + true, + ConfigKey.Scope.Global + ); + + /** + * List all registered KMS providers + * + * @return list of available providers + */ + List listKMSProviders(); + + /** + * Get a specific KMS provider by name + * + * @param name provider name + * @return the provider, or null if not found + */ + KMSProvider getKMSProvider(String name); + + /** + * Check if caller has permission to use a KMS key + * + * @param callerAccountId the caller's account ID + * @param key the KMS key + * @return true if caller has permission + */ + boolean hasPermission(Long callerAccountId, KMSKey key); + + /** + * Validates that the KMS key can be used for volume encryption: key exists, not deleted, + * owner account matches the volume owner, key state is Enabled, and key purpose is VOLUME_ENCRYPTION. + * No-op if kmsKeyId is null. + * + * @param owner the account that will own the volume + * @param kmsKeyId the KMS key database ID + * @param zoneId the zone ID of the target resource (volume/VM) + * @throws InvalidParameterValueException if key not found, disabled, wrong purpose, zone mismatch, or account mismatch + */ + void checkKmsKeyForVolumeEncryption(Account owner, Long kmsKeyId, Long zoneId); + + /** + * Unwrap a DEK by wrapped key ID, trying multiple KEK versions if needed + * + * @param wrappedKeyId the wrapped key database ID + * @return plaintext DEK (caller must zeroize!) + * @throws KMSException if unwrap fails + */ + byte[] unwrapKey(Long wrappedKeyId) throws KMSException; + + /** + * Generate and wrap a DEK using a specific KMS key UUID + * + * @param kmsKey the KMS key + * @param callerAccountId the caller's account ID + * @return wrapped key ready for database storage + * @throws KMSException if operation fails + */ + WrappedKey generateVolumeKeyWithKek(KMSKey kmsKey, Long callerAccountId) throws KMSException; + + /** + * Create a KMS key and return the response object. + * Handles validation, account resolution, and permission checks. + * + * @param cmd the create command with all parameters + * @return KMSKeyResponse + * @throws KMSException if creation fails + */ + KMSKeyResponse createKMSKey(CreateKMSKeyCmd cmd) throws KMSException; + + /** + * List KMS keys and return the response object. + * Handles validation and permission checks. + * + * @param cmd the list command with all parameters + * @return ListResponse with KMSKeyResponse objects + */ + ListResponse listKMSKeys(ListKMSKeysCmd cmd); + + /** + * Update a KMS key and return the response object. + * Handles validation and permission checks. + * + * @param cmd the update command with all parameters + * @return KMSKeyResponse + * @throws KMSException if update fails + */ + KMSKeyResponse updateKMSKey(UpdateKMSKeyCmd cmd) throws KMSException; + + boolean deleteKMSWrappedKey(Volume vol) throws KMSException; + + /** + * Delete a KMS key and return the response object. + * Handles validation and permission checks. + * + * @param cmd the delete command with all parameters + * @return SuccessResponse + * @throws KMSException if deletion fails + */ + SuccessResponse deleteKMSKey(DeleteKMSKeyCmd cmd) throws KMSException; + + /** + * Rotate KEK by creating new version and scheduling gradual re-encryption + * + * @param cmd the rotate command with all parameters + * @return New KEK version UUID + * @throws KMSException if rotation fails + */ + String rotateKMSKey(RotateKMSKeyCmd cmd) throws KMSException; + + /** + * Migrate passphrase-based volumes to KMS encryption + * + * @param cmd the migrate command with all parameters + * @return Number of volumes successfully migrated + * @throws KMSException if migration fails + */ + int migrateVolumesToKMS(MigrateVolumesToKMSCmd cmd) throws KMSException; + + /** + * Delete all KMS keys owned by an account (called during account cleanup) + * + * @param accountId the account ID + * @return true if all keys were successfully deleted + */ + boolean deleteKMSKeysByAccountId(Long accountId); + + /** + * Add a new HSM profile + * + * @param cmd the add command + * @return the created HSM profile + * @throws KMSException if addition fails + */ + HSMProfile addHSMProfile(CreateHSMProfileCmd cmd) throws KMSException; + + /** + * List HSM profiles + * + * @param cmd the list command + * @return list of HSM profiles + */ + ListResponse listHSMProfiles(ListHSMProfilesCmd cmd); + + /** + * Delete an HSM profile + * + * @param cmd the delete command + * @return true if deletion was successful + * @throws KMSException if deletion fails + */ + boolean deleteHSMProfile(DeleteHSMProfileCmd cmd) throws KMSException; + + /** + * Update an HSM profile + * + * @param cmd the update command + * @return the updated HSM profile + * @throws KMSException if update fails + */ + HSMProfile updateHSMProfile(UpdateHSMProfileCmd cmd) throws KMSException; + + /** + * Create a response object for an HSM profile + * + * @param profile the HSM profile + * @return the response object + */ + HSMProfileResponse createHSMProfileResponse(HSMProfile profile); +} diff --git a/api/src/main/java/org/apache/cloudstack/management/ManagementServerHost.java b/api/src/main/java/org/apache/cloudstack/management/ManagementServerHost.java index 54a53f39578d..7f81523dab7d 100644 --- a/api/src/main/java/org/apache/cloudstack/management/ManagementServerHost.java +++ b/api/src/main/java/org/apache/cloudstack/management/ManagementServerHost.java @@ -22,7 +22,7 @@ public interface ManagementServerHost extends InternalIdentity, Identity, ControlledEntity { enum State { - Up, Down, PreparingToShutDown, ReadyToShutDown, ShuttingDown + Up, Down, PreparingForMaintenance, Maintenance, PreparingForShutDown, ReadyToShutDown, ShuttingDown } long getMsid(); diff --git a/api/src/main/java/org/apache/cloudstack/network/RoutedIpv4Manager.java b/api/src/main/java/org/apache/cloudstack/network/RoutedIpv4Manager.java index 221a550ad630..9285331f41aa 100644 --- a/api/src/main/java/org/apache/cloudstack/network/RoutedIpv4Manager.java +++ b/api/src/main/java/org/apache/cloudstack/network/RoutedIpv4Manager.java @@ -158,7 +158,7 @@ public interface RoutedIpv4Manager extends PluggableService, Configurable { boolean isRoutedVpc(Vpc vpc); - boolean isVpcVirtualRouterGateway(VpcOffering vpcOffering); + boolean isValidGateway(VpcOffering vpcOffering); BgpPeer createBgpPeer(CreateBgpPeerCmd createBgpPeerCmd); diff --git a/api/src/main/java/org/apache/cloudstack/query/QueryService.java b/api/src/main/java/org/apache/cloudstack/query/QueryService.java index 5181ebe2b763..b6362e9a9c9b 100644 --- a/api/src/main/java/org/apache/cloudstack/query/QueryService.java +++ b/api/src/main/java/org/apache/cloudstack/query/QueryService.java @@ -32,6 +32,7 @@ import org.apache.cloudstack.api.command.admin.storage.ListImageStoresCmd; import org.apache.cloudstack.api.command.admin.storage.ListObjectStoragePoolsCmd; import org.apache.cloudstack.api.command.admin.storage.ListSecondaryStagingStoresCmd; +import org.apache.cloudstack.api.command.admin.storage.ListStorageAccessGroupsCmd; import org.apache.cloudstack.api.command.admin.storage.ListStoragePoolsCmd; import org.apache.cloudstack.api.command.admin.storage.ListStorageTagsCmd; import org.apache.cloudstack.api.command.admin.storage.heuristics.ListSecondaryStorageSelectorsCmd; @@ -40,6 +41,7 @@ import org.apache.cloudstack.api.command.user.account.ListProjectAccountsCmd; import org.apache.cloudstack.api.command.user.address.ListQuarantinedIpsCmd; import org.apache.cloudstack.api.command.user.affinitygroup.ListAffinityGroupsCmd; +import org.apache.cloudstack.api.command.user.backup.ListBackupServiceJobsCmd; import org.apache.cloudstack.api.command.user.bucket.ListBucketsCmd; import org.apache.cloudstack.api.command.user.event.ListEventsCmd; import org.apache.cloudstack.api.command.user.iso.ListIsosCmd; @@ -62,6 +64,7 @@ import org.apache.cloudstack.api.command.user.zone.ListZonesCmd; import org.apache.cloudstack.api.response.AccountResponse; import org.apache.cloudstack.api.response.AsyncJobResponse; +import org.apache.cloudstack.api.response.BackupServiceJobResponse; import org.apache.cloudstack.api.response.BucketResponse; import org.apache.cloudstack.api.response.DetailOptionsResponse; import org.apache.cloudstack.api.response.DiskOfferingResponse; @@ -87,6 +90,7 @@ import org.apache.cloudstack.api.response.SecurityGroupResponse; import org.apache.cloudstack.api.response.ServiceOfferingResponse; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.api.response.StorageAccessGroupResponse; import org.apache.cloudstack.api.response.StoragePoolResponse; import org.apache.cloudstack.api.response.StorageTagResponse; import org.apache.cloudstack.api.response.TemplateResponse; @@ -117,7 +121,7 @@ public interface QueryService { "Determines whether users can view certain VM settings. When set to empty, default value used is: rootdisksize, cpuOvercommitRatio, memoryOvercommitRatio, Message.ReservedCapacityFreed.Flag.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null); ConfigKey UserVMReadOnlyDetails = new ConfigKey<>(String.class, - "user.vm.readonly.details", "Advanced", "dataDiskController, rootDiskController", + "user.vm.readonly.details", "Advanced", "dataDiskController, rootDiskController, backupValidationCommandTimeout, backupValidationScreenshotWait, backupValidationBootTimeout", "List of read-only VM settings/details as comma separated string", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.CSV, null, ""); ConfigKey SortKeyAscending = new ConfigKey<>("Advanced", Boolean.class, "sortkey.algorithm", "true", @@ -143,6 +147,8 @@ public interface QueryService { ListResponse searchForUsers(Long domainId, boolean recursive) throws PermissionDeniedException; + List searchForAccessibleUsers(); + ListResponse searchForEvents(ListEventsCmd cmd); ListResponse listTags(ListTagsCmd cmd); @@ -201,6 +207,8 @@ public interface QueryService { ListResponse searchForStorageTags(ListStorageTagsCmd cmd); + ListResponse searchForStorageAccessGroups(ListStorageAccessGroupsCmd cmd); + ListResponse searchForHostTags(ListHostTagsCmd cmd); ListResponse listManagementServers(ListMgmtsCmd cmd); @@ -218,4 +226,6 @@ public interface QueryService { ListResponse searchForObjectStores(ListObjectStoragePoolsCmd listObjectStoragePoolsCmd); ListResponse searchForBuckets(ListBucketsCmd listBucketsCmd); + + ListResponse listBackupServiceJobs(ListBackupServiceJobsCmd cmd); } diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java similarity index 66% rename from api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java rename to api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java index b1e7df3c39cf..4bd39211a27f 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMSchedule.java +++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceSchedule.java @@ -16,20 +16,31 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule; +package org.apache.cloudstack.schedule; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; import java.time.ZoneId; import java.util.Date; -public interface VMSchedule extends Identity, InternalIdentity { - enum Action { - START, STOP, REBOOT, FORCE_STOP, FORCE_REBOOT +public interface ResourceSchedule extends Identity, InternalIdentity { + + /** + * Common contract for scheduler actions. Each provider defines its own enum + * implementing this interface so the generic machinery can call {@link #name()} + * and {@link #getEventType()} without knowing the concrete type. + */ + interface Action { + String name(); + + String getEventType(); } - long getVmId(); + ApiCommandResourceType getResourceType(); + + long getResourceId(); String getDescription(); @@ -37,7 +48,7 @@ enum Action { String getTimeZone(); - Action getAction(); + String getActionName(); boolean getEnabled(); diff --git a/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java new file mode 100644 index 000000000000..fb650a6cf68d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleManager.java @@ -0,0 +1,45 @@ +/* + * 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.schedule; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; + +import java.util.Date; +import java.util.List; +import java.util.Map; + +public interface ResourceScheduleManager { + + ResourceScheduleResponse createSchedule(ApiCommandResourceType resourceType, String resourceUuid, + String description, String schedule, String timeZone, String action, + Date startDate, Date endDate, boolean enabled, Map details); + + ResourceScheduleResponse updateSchedule(Long id, String description, String schedule, String timeZone, + Date startDate, Date endDate, Boolean enabled, Map details); + + ListResponse listSchedule(Long id, List ids, ApiCommandResourceType resourceType, + String resourceUuid, String action, Boolean enabled, + Long startIndex, Long pageSize); + + Long removeSchedule(ApiCommandResourceType resourceType, String resourceUuid, Long id, List ids); + + void removeSchedulesForResource(ApiCommandResourceType resourceType, long resourceId); +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java similarity index 77% rename from api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java rename to api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java index d7a18b768279..a7bf4d9aa05e 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJob.java +++ b/api/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJob.java @@ -16,23 +16,26 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule; +package org.apache.cloudstack.schedule; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.Identity; import org.apache.cloudstack.api.InternalIdentity; import java.util.Date; -public interface VMScheduledJob extends Identity, InternalIdentity { - long getVmId(); +public interface ResourceScheduledJob extends Identity, InternalIdentity { + ApiCommandResourceType getResourceType(); - long getVmScheduleId(); + long getResourceId(); + + long getScheduleId(); Long getAsyncJobId(); void setAsyncJobId(long asyncJobId); - VMSchedule.Action getAction(); + String getActionName(); Date getScheduledTime(); } diff --git a/api/src/main/java/org/apache/cloudstack/schedule/autoscale/AutoScaleScheduleAction.java b/api/src/main/java/org/apache/cloudstack/schedule/autoscale/AutoScaleScheduleAction.java new file mode 100644 index 000000000000..c05dafe88796 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/schedule/autoscale/AutoScaleScheduleAction.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.schedule.autoscale; + +import com.cloud.event.EventTypes; +import org.apache.cloudstack.schedule.ResourceSchedule; + +public enum AutoScaleScheduleAction implements ResourceSchedule.Action { + UPDATE { + @Override + public String getEventType() { + return EventTypes.EVENT_AUTOSCALEVMGROUP_SCHEDULE_UPDATE; + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java b/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java new file mode 100644 index 000000000000..1648305803ff --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/schedule/vm/VMScheduleAction.java @@ -0,0 +1,45 @@ +/* + * 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.schedule.vm; + +import com.cloud.event.EventTypes; +import org.apache.cloudstack.schedule.ResourceSchedule; + +public enum VMScheduleAction implements ResourceSchedule.Action { + START { + @Override + public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_START; } + }, + STOP { + @Override + public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_STOP; } + }, + REBOOT { + @Override + public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_REBOOT; } + }, + FORCE_STOP { + @Override + public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_STOP; } + }, + FORCE_REBOOT { + @Override + public String getEventType() { return EventTypes.EVENT_VM_SCHEDULE_FORCE_REBOOT; } + }; +} diff --git a/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/HeuristicType.java b/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/HeuristicType.java index f23e4b0b633b..489e9bf54f23 100644 --- a/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/HeuristicType.java +++ b/api/src/main/java/org/apache/cloudstack/secstorage/heuristics/HeuristicType.java @@ -18,8 +18,8 @@ /** * The type of the heuristic used in the allocation process of secondary storage resources. - * Valid options are: {@link #ISO}, {@link #SNAPSHOT}, {@link #TEMPLATE} and {@link #VOLUME} + * Valid options are: {@link #ISO}, {@link #SNAPSHOT}, {@link #TEMPLATE}, {@link #VOLUME} and {@link #BACKUP} */ public enum HeuristicType { - ISO, SNAPSHOT, TEMPLATE, VOLUME + ISO, SNAPSHOT, TEMPLATE, VOLUME, BACKUP } diff --git a/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java b/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java index 7e1361d1e71f..8c164133db88 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java +++ b/api/src/main/java/org/apache/cloudstack/storage/object/BucketApiService.java @@ -22,10 +22,59 @@ import com.cloud.user.Account; import org.apache.cloudstack.api.command.user.bucket.CreateBucketCmd; import org.apache.cloudstack.api.command.user.bucket.UpdateBucketCmd; +import org.apache.cloudstack.framework.config.ConfigKey; public interface BucketApiService { + ConfigKey DefaultMaxAccountBuckets = new ConfigKey("Account Defaults", Long.class, + "max.account.buckets", + "20", + "The default maximum number of buckets that can be created for an account", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxAccountObjectStorage = new ConfigKey("Account Defaults", Long.class, + "max.account.object.storage", + "400", + "The default maximum object storage space (in GiB) that can be used for an account", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxProjectBuckets = new ConfigKey("Project Defaults", Long.class, + "max.project.buckets", + "20", + "The default maximum number of buckets that can be created for a project", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxProjectObjectStorage = new ConfigKey("Project Defaults", Long.class, + "max.project.object.storage", + "400", + "The default maximum object storage space (in GiB) that can be used for a project", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxDomainBuckets = new ConfigKey("Domain Defaults", Long.class, + "max.domain.buckets", + "20", + "The default maximum number of buckets that can be created for a domain", + false, + ConfigKey.Scope.Global, + null); + + ConfigKey DefaultMaxDomainObjectStorage = new ConfigKey("Domain Defaults", Long.class, + "max.domain.object.storage", + "400", + "The default maximum object storage space (in GiB) that can be used for a domain", + false, + ConfigKey.Scope.Global, + null); + /** * Creates the database object for a Bucket based on the given criteria * @@ -46,9 +95,9 @@ public interface BucketApiService { */ Bucket createBucket(CreateBucketCmd cmd); - boolean deleteBucket(long bucketId, Account caller); + boolean deleteBucket(long bucketId, Account caller) throws ResourceAllocationException; - boolean updateBucket(UpdateBucketCmd cmd, Account caller); + boolean updateBucket(UpdateBucketCmd cmd, Account caller) throws ResourceAllocationException; void getBucketUsage(); } diff --git a/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java b/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java index 21184de27a26..3e349cf0bd39 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java +++ b/api/src/main/java/org/apache/cloudstack/storage/sharedfs/SharedFSService.java @@ -23,6 +23,10 @@ import org.apache.cloudstack.api.command.user.storage.sharedfs.ChangeSharedFSServiceOfferingCmd; import org.apache.cloudstack.api.command.user.storage.sharedfs.CreateSharedFSCmd; import org.apache.cloudstack.api.command.user.storage.sharedfs.DestroySharedFSCmd; +import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd; +import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.SharedFSResponse; import com.cloud.exception.InsufficientCapacityException; import com.cloud.exception.ManagementServerException; @@ -31,11 +35,6 @@ import com.cloud.exception.ResourceUnavailableException; import com.cloud.exception.VirtualMachineMigrationException; -import org.apache.cloudstack.api.command.user.storage.sharedfs.ListSharedFSCmd; -import org.apache.cloudstack.api.command.user.storage.sharedfs.UpdateSharedFSCmd; -import org.apache.cloudstack.api.response.SharedFSResponse; -import org.apache.cloudstack.api.response.ListResponse; - public interface SharedFSService { List getSharedFSProviders(); @@ -69,4 +68,10 @@ public interface SharedFSService { SharedFS recoverSharedFS(Long sharedFSId); void deleteSharedFS(Long sharedFSId); + + SharedFS getSharedFSByUuid(String uuid); + + SharedFS getSharedFSForVmId(long vmId); + + SharedFS updateSharedFSPostRestore(long sharedFsId, long volumeId); } diff --git a/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java b/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java index 5f69f3e46e73..13f01b4944ad 100644 --- a/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java +++ b/api/src/main/java/org/apache/cloudstack/storage/volume/VolumeImportUnmanageService.java @@ -25,17 +25,28 @@ import org.apache.cloudstack.api.response.ListResponse; import org.apache.cloudstack.api.response.VolumeForImportResponse; import org.apache.cloudstack.api.response.VolumeResponse; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; import java.util.Arrays; import java.util.List; -public interface VolumeImportUnmanageService extends PluggableService { +public interface VolumeImportUnmanageService extends PluggableService, Configurable { List SUPPORTED_HYPERVISORS = Arrays.asList(Hypervisor.HypervisorType.KVM, Hypervisor.HypervisorType.VMware); List SUPPORTED_STORAGE_POOL_TYPES_FOR_KVM = Arrays.asList(Storage.StoragePoolType.NetworkFilesystem, - Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.RBD); + Storage.StoragePoolType.Filesystem, Storage.StoragePoolType.RBD, Storage.StoragePoolType.SharedMountPoint); + + ConfigKey AllowImportVolumeWithBackingFile = new ConfigKey<>(Boolean.class, + "allow.import.volume.with.backing.file", + "Advanced", + "false", + "If enabled, allows QCOW2 volumes with backing files to be imported or unmanaged", + true, + ConfigKey.Scope.Global, + null); ListResponse listVolumesForImport(ListVolumesForImportCmd cmd); diff --git a/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java b/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java index b4bede248907..7e718413118e 100644 --- a/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java +++ b/api/src/main/java/org/apache/cloudstack/userdata/UserDataManager.java @@ -22,6 +22,8 @@ import com.cloud.utils.component.Manager; +import java.io.IOException; + public interface UserDataManager extends Manager, Configurable { String VM_USERDATA_MAX_LENGTH_STRING = "vm.userdata.max.length"; ConfigKey VM_USERDATA_MAX_LENGTH = new ConfigKey<>("Advanced", Integer.class, VM_USERDATA_MAX_LENGTH_STRING, "32768", @@ -29,4 +31,14 @@ public interface UserDataManager extends Manager, Configurable { String concatenateUserData(String userdata1, String userdata2, String userdataProvider); String validateUserData(String userData, BaseCmd.HTTPMethod httpmethod); + + /** + * This method validates the user data uuid for system VMs and returns the user data + * after compression and base64 encoding for the system VM to consume. + * + * @param userDataUuid + * @return a String containing the user data after compression and base64 encoding + * @throws IOException + */ + String validateAndGetUserDataForSystemVM(String userDataUuid) throws IOException; } diff --git a/api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java new file mode 100644 index 000000000000..5bb51dfcbcb9 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTask.java @@ -0,0 +1,41 @@ +/* + * 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.vm; + +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; + +public interface ImportVmTask extends Identity, InternalIdentity { + enum Step { + Prepare, CloningInstance, ConvertingInstance, Importing, Completed + } + + enum TaskState { + Running, Completed, Failed; + + public static TaskState getValue(String state) { + for (TaskState s : TaskState.values()) { + if (s.name().equalsIgnoreCase(state)) { + return s; + } + } + throw new IllegalArgumentException("Invalid task state: " + state); + } + } +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManager.java b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManager.java new file mode 100644 index 000000000000..233511fbce8d --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/vm/ImportVmTasksManager.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.vm; + +import com.cloud.dc.DataCenter; +import com.cloud.host.Host; +import com.cloud.user.Account; +import org.apache.cloudstack.api.command.admin.vm.ListImportVMTasksCmd; +import org.apache.cloudstack.api.response.ImportVMTaskResponse; +import org.apache.cloudstack.api.response.ListResponse; + +public interface ImportVmTasksManager { + + ListResponse listImportVMTasks(ListImportVMTasksCmd cmd); + + ImportVmTask createImportVMTaskRecord(DataCenter zone, Account owner, long userId, String displayName, + String vcenter, String datacenterName, String sourceVMName, + Host convertHost, Host importHost); + + void updateImportVMTaskStep(ImportVmTask importVMTaskVO, DataCenter zone, Account owner, Host convertHost, + Host importHost, Long vmId, ImportVmTask.Step step); + + void updateImportVMTaskErrorState(ImportVmTask importVMTaskVO, ImportVmTask.TaskState state, String errorMsg); +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanageVMService.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanageVMService.java index 2315e5f2e95f..70d8795f3058 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanageVMService.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanageVMService.java @@ -17,11 +17,14 @@ package org.apache.cloudstack.vm; +import com.cloud.utils.Pair; + public interface UnmanageVMService { /** * Unmanage a guest VM from CloudStack - * @return true if the VM is successfully unmanaged, false if not. + * + * @return (true if successful, false if not, hostUuid) if the VM is successfully unmanaged. */ - boolean unmanageVMInstance(long vmId); + Pair unmanageVMInstance(long vmId, Long paramHostId, boolean isForced); } diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java index bba97dff71cb..cbb7c4de698a 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedInstanceTO.java @@ -55,6 +55,9 @@ public enum PowerState { private String hostName; + private String hypervisorType; + private String hostHypervisorVersion; + private List disks; private List nics; @@ -168,6 +171,22 @@ public void setHostName(String hostName) { this.hostName = hostName; } + public String getHypervisorType() { + return hypervisorType; + } + + public void setHypervisorType(String hypervisorType) { + this.hypervisorType = hypervisorType; + } + + public String getHostHypervisorVersion() { + return hostHypervisorVersion; + } + + public void setHostHypervisorVersion(String hostHypervisorVersion) { + this.hostHypervisorVersion = hostHypervisorVersion; + } + public List getDisks() { return disks; } diff --git a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java index b6233b9c2704..e1963313eb6f 100644 --- a/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java +++ b/api/src/main/java/org/apache/cloudstack/vm/UnmanagedVMsManager.java @@ -26,6 +26,8 @@ public interface UnmanagedVMsManager extends VmImportService, UnmanageVMService, PluggableService, Configurable { + String VM_IMPORT_DEFAULT_TEMPLATE_NAME = "system-default-vm-import-dummy-template.iso"; + String KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME = "kvm-default-vm-import-dummy-template"; ConfigKey UnmanageVMPreserveNic = new ConfigKey<>("Advanced", Boolean.class, "unmanage.vm.preserve.nics", "false", "If set to true, do not remove VM nics (and its MAC addresses) when unmanaging a VM, leaving them allocated but not reserved. " + "If set to false, nics are removed and MAC addresses can be reassigned", true, ConfigKey.Scope.Zone); diff --git a/api/src/main/java/org/apache/cloudstack/vm/lease/VMLeaseManager.java b/api/src/main/java/org/apache/cloudstack/vm/lease/VMLeaseManager.java new file mode 100644 index 000000000000..b67026953059 --- /dev/null +++ b/api/src/main/java/org/apache/cloudstack/vm/lease/VMLeaseManager.java @@ -0,0 +1,61 @@ +/* + * 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.vm.lease; + +import com.cloud.utils.component.Manager; +import org.apache.cloudstack.framework.config.ConfigKey; + +import java.util.List; + +public interface VMLeaseManager extends Manager { + + int MAX_LEASE_DURATION_DAYS = 365_00; // 100 years + + enum ExpiryAction { + STOP, + DESTROY + } + + enum LeaseActionExecution { + PENDING, + DISABLED, + DONE, + CANCELLED + } + + ConfigKey InstanceLeaseEnabled = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Boolean.class, + "instance.lease.enabled", "false", "Indicates whether to enable the Instance lease," + + " will be applicable only on instances created after lease is enabled. Disabling the feature cancels lease on existing instances with lease." + + " Re-enabling feature will not cause lease expiry actions on grandfathered instances", + true, List.of(ConfigKey.Scope.Global)); + + ConfigKey InstanceLeaseSchedulerInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, + "instance.lease.scheduler.interval", "3600", "VM Lease Scheduler interval in seconds", + false, List.of(ConfigKey.Scope.Global)); + + ConfigKey InstanceLeaseExpiryEventSchedulerInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, + "instance.lease.eventscheduler.interval", "86400", "Lease expiry event Scheduler interval in seconds", + false, List.of(ConfigKey.Scope.Global)); + + ConfigKey InstanceLeaseExpiryEventDaysBefore = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, + "instance.lease.expiryevent.daysbefore", "7", "Indicates how many days in advance, expiry events will be created before expiry.", + true, List.of(ConfigKey.Scope.Global)); + + void onLeaseFeatureToggle(); +} diff --git a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java b/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java deleted file mode 100644 index 6aca05b58d5e..000000000000 --- a/api/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleManager.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.cloudstack.vm.schedule; - -import org.apache.cloudstack.api.command.user.vm.CreateVMScheduleCmd; -import org.apache.cloudstack.api.command.user.vm.DeleteVMScheduleCmd; -import org.apache.cloudstack.api.command.user.vm.ListVMScheduleCmd; -import org.apache.cloudstack.api.command.user.vm.UpdateVMScheduleCmd; -import org.apache.cloudstack.api.response.ListResponse; -import org.apache.cloudstack.api.response.VMScheduleResponse; - -public interface VMScheduleManager { - VMScheduleResponse createSchedule(CreateVMScheduleCmd createVMScheduleCmd); - - VMScheduleResponse createResponse(VMSchedule vmSchedule); - - ListResponse listSchedule(ListVMScheduleCmd listVMScheduleCmd); - - VMScheduleResponse updateSchedule(UpdateVMScheduleCmd updateVMScheduleCmd); - - long removeScheduleByVmId(long vmId, boolean expunge); - - Long removeSchedule(DeleteVMScheduleCmd deleteVMScheduleCmd); -} diff --git a/api/src/test/java/com/cloud/cpu/CPUTest.java b/api/src/test/java/com/cloud/cpu/CPUTest.java index dfedf21864cc..0a059cf9a90a 100644 --- a/api/src/test/java/com/cloud/cpu/CPUTest.java +++ b/api/src/test/java/com/cloud/cpu/CPUTest.java @@ -28,6 +28,7 @@ public void testCPUArchGetType() { assertEquals("i686", CPU.CPUArch.x86.getType()); assertEquals("x86_64", CPU.CPUArch.amd64.getType()); assertEquals("aarch64", CPU.CPUArch.arm64.getType()); + assertEquals("s390x", CPU.CPUArch.s390x.getType()); } @Test @@ -35,6 +36,7 @@ public void testCPUArchGetBits() { assertEquals(32, CPU.CPUArch.x86.getBits()); assertEquals(64, CPU.CPUArch.amd64.getBits()); assertEquals(64, CPU.CPUArch.arm64.getBits()); + assertEquals(64, CPU.CPUArch.s390x.getBits()); } @Test @@ -42,6 +44,7 @@ public void testCPUArchFromTypeWithValidValues() { assertEquals(CPU.CPUArch.x86, CPU.CPUArch.fromType("i686")); assertEquals(CPU.CPUArch.amd64, CPU.CPUArch.fromType("x86_64")); assertEquals(CPU.CPUArch.arm64, CPU.CPUArch.fromType("aarch64")); + assertEquals(CPU.CPUArch.s390x, CPU.CPUArch.fromType("s390x")); } @Test @@ -61,7 +64,7 @@ public void testCPUArchFromTypeWithInvalidValue() { @Test public void testCPUArchGetTypesAsCSV() { - String expectedCSV = "i686,x86_64,aarch64"; + String expectedCSV = "i686,x86_64,aarch64,s390x"; assertEquals(expectedCSV, CPU.CPUArch.getTypesAsCSV()); } } diff --git a/api/src/test/java/com/cloud/network/NetworkTest.java b/api/src/test/java/com/cloud/network/NetworkTest.java new file mode 100644 index 000000000000..dba4d1fb7eb5 --- /dev/null +++ b/api/src/test/java/com/cloud/network/NetworkTest.java @@ -0,0 +1,73 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.network; + +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + + +public class NetworkTest { + + @Test + public void testProviderContains() { + List providers = new ArrayList<>(); + providers.add(Network.Provider.VirtualRouter); + + // direct instance present + assertTrue("List should contain VirtualRouter provider", providers.contains(Network.Provider.VirtualRouter)); + + // resolved provider by name (registered provider) + Network.Provider resolved = Network.Provider.getProvider("VirtualRouter"); + assertNotNull("Resolved provider should not be null", resolved); + assertTrue("List should contain resolved VirtualRouter provider", providers.contains(resolved)); + + // transient provider with same name should be considered equal (equals by name) + Network.Provider transientProvider = Network.Provider.createTransientProvider("NetworkExtension"); + assertFalse("List should not contain the transient provider", providers.contains(transientProvider)); + + providers.add(transientProvider); + assertTrue("List should contain the transient provider", providers.contains(transientProvider)); + + // another transient provider with same name should be considered equal + Network.Provider transientProviderNew = Network.Provider.createTransientProvider("NetworkExtension"); + assertTrue("List should contain the new transient provider with same name", providers.contains(transientProviderNew)); + } + + @Test + public void testCustomActionServiceLookup() { + Network.Service customAction = Network.Service.getService("CustomAction"); + assertNotNull("CustomAction service should be available", customAction); + assertTrue("CustomAction should be part of the supported services list", + Network.Service.listAllServices().contains(customAction)); + } + + @Test + public void testTransientProviderIsNotGloballyRegistered() { + Network.Provider transientProvider = Network.Provider.createTransientProvider("TransientOnly"); + assertNotNull(transientProvider); + assertNull("Transient provider should not be retrievable from the global registry", + Network.Provider.getProvider("TransientOnly")); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/acl/RuleTest.java b/api/src/test/java/org/apache/cloudstack/acl/RuleTest.java index 79e6127d29ad..b99ba48c66dc 100644 --- a/api/src/test/java/org/apache/cloudstack/acl/RuleTest.java +++ b/api/src/test/java/org/apache/cloudstack/acl/RuleTest.java @@ -17,13 +17,46 @@ package org.apache.cloudstack.acl; import com.cloud.exception.InvalidParameterValueException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.Pattern; +import org.apache.cloudstack.api.APICommand; import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Test; import java.util.Arrays; +import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AnnotationTypeFilter; public class RuleTest { + private static List apiNames; + private static List apiRules; + private static ClassPathScanningCandidateComponentProvider provider = new ClassPathScanningCandidateComponentProvider(false); + + @BeforeClass + public static void setup() { + provider.addIncludeFilter(new AnnotationTypeFilter(APICommand.class)); + Set beanDefinitions = provider.findCandidateComponents("org.apache.cloudstack.api"); + + apiNames = new ArrayList<>(); + apiRules = new ArrayList<>(); + for(BeanDefinition bd : beanDefinitions) { + if (bd instanceof AnnotatedBeanDefinition) { + Map annotationAttributeMap = ((AnnotatedBeanDefinition) bd).getMetadata() + .getAnnotationAttributes(APICommand.class.getName()); + String apiName = annotationAttributeMap.get("name").toString(); + apiNames.add(apiName); + apiRules.add(new Rule(apiName)); + } + } + } + @Test public void testToString() throws Exception { Rule rule = new Rule("someString"); @@ -31,21 +64,89 @@ public void testToString() throws Exception { } @Test - public void testMatchesEmpty() throws Exception { - Rule rule = new Rule("someString"); - Assert.assertFalse(rule.matches("")); + public void ruleMatchesTestNoMatchesOnEmptyString() throws Exception { + String testCmd = ""; + List matches = new ArrayList<>(); + for (Rule rule : apiRules) { + if (rule.matches(testCmd)) { + matches.add(rule.getRuleString()); + } + } + + Assert.assertEquals(matches.size(), 0); } @Test - public void testMatchesNull() throws Exception { - Rule rule = new Rule("someString"); - Assert.assertFalse(rule.matches(null)); + public void ruleMatchesTestNoMatchesOnNull() throws Exception { + List matches = new ArrayList<>(); + for (Rule rule : apiRules) { + if (rule.matches(null)) { + matches.add(rule.getRuleString()); + } + } + + Assert.assertTrue(matches.isEmpty()); } @Test - public void testMatchesSpace() throws Exception { - Rule rule = new Rule("someString"); - Assert.assertFalse(rule.matches(" ")); + public void ruleMatchesTestNoMatchesOnSpaceCharacter() throws Exception { + String testCmd = " "; + List matches = new ArrayList<>(); + for (Rule rule : apiRules) { + if (rule.matches(testCmd)) { + matches.add(rule.getRuleString()); + } + } + + Assert.assertTrue(matches.isEmpty()); + } + + @Test + public void ruleMatchesTestWildCardOnEndWorksAsNormalRegex() { + setup(); + Pattern regexPattern = Pattern.compile("list.*"); + Rule acsRegexRule = new Rule("list*"); + + List nonMatches = new ArrayList<>(); + for (String apiName : apiNames) { + if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) { + nonMatches.add(apiName); + } + } + + Assert.assertTrue(nonMatches.isEmpty()); + } + + @Test + public void ruleMatchesTestWildCardOnMiddleWorksAsNormalRegex() { + setup(); + Pattern regexPattern = Pattern.compile("list.*s"); + Rule acsRegexRule = new Rule("list*s"); + + List nonMatches = new ArrayList<>(); + for (String apiName : apiNames) { + if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) { + nonMatches.add(apiName); + } + } + + Assert.assertTrue(nonMatches.isEmpty()); + } + + @Test + public void ruleMatchesTestWildCardOnStartWorksAsNormalRegex() { + setup(); + Pattern regexPattern = Pattern.compile(".*User"); + Rule acsRegexRule = new Rule("*User"); + + List nonMatches = new ArrayList<>(); + for (String apiName : apiNames) { + if (acsRegexRule.matches(apiName) != regexPattern.matcher(apiName).matches()) { + nonMatches.add(apiName); + } + } + + Assert.assertTrue(nonMatches.isEmpty()); } @Test @@ -73,7 +174,25 @@ public void testMatchesWildcardMiddle() throws Exception { } @Test - public void testValidateRuleWithValidData() throws Exception { + public void ruleMatchesTestWildcardOnRuleAndCommand() throws Exception { + Rule rule = new Rule("*"); + Assert.assertTrue(rule.matches("list*")); + } + + @Test + public void ruleMatchesTestWildcardOnRuleAndCommandNotAllowed() throws Exception { + Rule rule = new Rule("list*"); + Assert.assertFalse(rule.matches("*")); + } + + @Test + public void ruleMatchesTestWithMultipleStars() throws Exception { + Rule rule = new Rule("list***"); + Assert.assertFalse(rule.matches("api")); + } + + @Test + public void testRuleToStringWithValidStrings() throws Exception { for (String rule : Arrays.asList("a", "1", "someApi", "someApi321", "123SomeApi", "prefix*", "*middle*", "*Suffix", "*", "**", "f***", "m0nk3yMa**g1c*")) { @@ -82,7 +201,7 @@ public void testValidateRuleWithValidData() throws Exception { } @Test - public void testValidateRuleWithInvalidData() throws Exception { + public void testRuleToStringWithInvalidStrings() throws Exception { for (String rule : Arrays.asList(null, "", " ", " ", "\n", "\t", "\r", "\"", "\'", "^someApi$", "^someApi", "some$", "some-Api;", "some,Api", "^", "$", "^$", ".*", "\\w+", "r**l3rd0@Kr3", "j@s1n|+|0ȷ", diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmdTest.java new file mode 100644 index 000000000000..a1412d5a76a7 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/backup/CloneBackupOfferingCmdTest.java @@ -0,0 +1,301 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.backup; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.BackupOfferingResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupOffering; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class CloneBackupOfferingCmdTest { + + private CloneBackupOfferingCmd cloneBackupOfferingCmd; + + @Mock + private BackupManager backupManager; + + @Mock + private ResponseGenerator responseGenerator; + + @Mock + private BackupOffering mockBackupOffering; + + @Mock + private BackupOfferingResponse mockBackupOfferingResponse; + + @Before + public void setUp() { + cloneBackupOfferingCmd = new CloneBackupOfferingCmd(); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "backupManager", backupManager); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "_responseGenerator", responseGenerator); + } + + @Test + public void testGetSourceOfferingId() { + Long sourceOfferingId = 999L; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId); + assertEquals(sourceOfferingId, cloneBackupOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testGetName() { + String name = "ClonedBackupOffering"; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", name); + assertEquals(name, cloneBackupOfferingCmd.getName()); + } + + @Test + public void testGetDescription() { + String description = "Cloned Backup Offering Description"; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", description); + assertEquals(description, cloneBackupOfferingCmd.getDescription()); + } + + @Test + public void testGetZoneId() { + Long zoneId = 123L; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", zoneId); + assertEquals(zoneId, cloneBackupOfferingCmd.getZoneId()); + } + + @Test + public void testGetExternalId() { + String externalId = "external-backup-123"; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "externalId", externalId); + assertEquals(externalId, cloneBackupOfferingCmd.getExternalId()); + } + + @Test + public void testGetAllowUserDrivenBackups() { + Boolean allowUserDrivenBackups = true; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", allowUserDrivenBackups); + assertEquals(allowUserDrivenBackups, cloneBackupOfferingCmd.getUserDrivenBackups()); + } + + @Test + public void testAllowUserDrivenBackupsDefaultTrue() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", null); + Boolean result = cloneBackupOfferingCmd.getUserDrivenBackups(); + assertTrue(result == null || result); + } + + @Test + public void testAllowUserDrivenBackupsFalse() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", false); + assertEquals(Boolean.FALSE, cloneBackupOfferingCmd.getUserDrivenBackups()); + } + + @Test + public void testExecuteSuccess() throws Exception { + Long sourceOfferingId = 999L; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + + when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))).thenReturn(mockBackupOffering); + when(responseGenerator.createBackupOfferingResponse(mockBackupOffering)).thenReturn(mockBackupOfferingResponse); + + cloneBackupOfferingCmd.execute(); + + assertNotNull(cloneBackupOfferingCmd.getResponseObject()); + assertEquals(mockBackupOfferingResponse, cloneBackupOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteFailure() throws Exception { + Long sourceOfferingId = 999L; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))).thenReturn(null); + + try { + cloneBackupOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + assertEquals("Failed to clone backup offering", e.getMessage()); + } + } + + @Test + public void testExecuteWithInvalidParameterException() throws Exception { + Long sourceOfferingId = 999L; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))) + .thenThrow(new InvalidParameterValueException("Invalid source offering ID")); + + try { + cloneBackupOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode()); + assertEquals("Invalid source offering ID", e.getMessage()); + } + } + + @Test + public void testExecuteWithCloudRuntimeException() throws Exception { + Long sourceOfferingId = 999L; + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))) + .thenThrow(new CloudRuntimeException("Runtime error during clone")); + + try { + cloneBackupOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + assertEquals("Runtime error during clone", e.getMessage()); + } + } + + @Test + public void testExecuteSuccessWithAllParameters() throws Exception { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Test Description"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", 123L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "externalId", "ext-123"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", true); + + when(backupManager.cloneBackupOffering(any(CloneBackupOfferingCmd.class))).thenReturn(mockBackupOffering); + when(responseGenerator.createBackupOfferingResponse(mockBackupOffering)).thenReturn(mockBackupOfferingResponse); + + cloneBackupOfferingCmd.execute(); + + assertNotNull(cloneBackupOfferingCmd.getResponseObject()); + assertEquals(mockBackupOfferingResponse, cloneBackupOfferingCmd.getResponseObject()); + } + + @Test + public void testCloneWithAllParameters() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Cloned backup offering for testing"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", 123L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "externalId", "external-backup-123"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", true); + + assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId()); + assertEquals("ClonedBackupOffering", cloneBackupOfferingCmd.getName()); + assertEquals("Cloned backup offering for testing", cloneBackupOfferingCmd.getDescription()); + assertEquals(Long.valueOf(123L), cloneBackupOfferingCmd.getZoneId()); + assertEquals("external-backup-123", cloneBackupOfferingCmd.getExternalId()); + assertEquals(Boolean.TRUE, cloneBackupOfferingCmd.getUserDrivenBackups()); + } + + @Test + public void testCloneWithMinimalParameters() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Description"); + + assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId()); + assertEquals("ClonedBackupOffering", cloneBackupOfferingCmd.getName()); + assertEquals("Description", cloneBackupOfferingCmd.getDescription()); + + assertNull(cloneBackupOfferingCmd.getZoneId()); + assertNull(cloneBackupOfferingCmd.getExternalId()); + } + + @Test + public void testSourceOfferingIdNullByDefault() { + assertNull(cloneBackupOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testNameNullByDefault() { + assertNull(cloneBackupOfferingCmd.getName()); + } + + @Test + public void testDescriptionNullByDefault() { + assertNull(cloneBackupOfferingCmd.getDescription()); + } + + @Test + public void testZoneIdNullByDefault() { + assertNull(cloneBackupOfferingCmd.getZoneId()); + } + + @Test + public void testExternalIdNullByDefault() { + assertNull(cloneBackupOfferingCmd.getExternalId()); + } + + @Test + public void testCloneBackupOfferingInheritingZone() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone with inherited zone"); + + assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId()); + assertNull(cloneBackupOfferingCmd.getZoneId()); + } + + @Test + public void testCloneBackupOfferingInheritingExternalId() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone with inherited external ID"); + + assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId()); + assertNull(cloneBackupOfferingCmd.getExternalId()); + } + + @Test + public void testCloneBackupOfferingOverridingZone() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone with new zone"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "zoneId", 456L); + + assertEquals(Long.valueOf(999L), cloneBackupOfferingCmd.getSourceOfferingId()); + assertEquals(Long.valueOf(456L), cloneBackupOfferingCmd.getZoneId()); + } + + @Test + public void testCloneBackupOfferingDisallowUserDrivenBackups() { + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "sourceOfferingId", 999L); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "name", "ClonedBackupOffering"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "description", "Clone without user-driven backups"); + ReflectionTestUtils.setField(cloneBackupOfferingCmd, "userDrivenBackups", false); + + assertEquals(Boolean.FALSE, cloneBackupOfferingCmd.getUserDrivenBackups()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmdTest.java new file mode 100644 index 000000000000..af53a539e670 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/cluster/ListClustersCmdTest.java @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.cluster; + +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.api.response.ClusterResponse; +import org.apache.cloudstack.extension.Extension; +import org.apache.cloudstack.extension.ExtensionHelper; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.hypervisor.Hypervisor; + +@RunWith(MockitoJUnitRunner.class) +public class ListClustersCmdTest { + + ExtensionHelper extensionHelper; + ListClustersCmd listClustersCmd = new ListClustersCmd(); + + @Before + public void setUp() { + extensionHelper = mock(ExtensionHelper.class); + listClustersCmd.extensionHelper = extensionHelper; + } + + @Test + public void updateClustersExtensions_emptyList_noAction() { + listClustersCmd.updateClustersExtensions(Collections.emptyList()); + // No exception, nothing to verify + } + + @Test + public void updateClustersExtensions_nullList_noAction() { + listClustersCmd.updateClustersExtensions(null); + // No exception, nothing to verify + } + + @Test + public void updateClustersExtensions_withClusterResponses_setsExtension() { + ClusterResponse cluster1 = mock(ClusterResponse.class); + ClusterResponse cluster2 = mock(ClusterResponse.class); + when(cluster1.getInternalId()).thenReturn(1L); + when(cluster1.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.External.name()); + when(cluster2.getInternalId()).thenReturn(2L); + when(cluster2.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.External.name()); + Extension ext1 = mock(Extension.class); + when(ext1.getUuid()).thenReturn("a"); + Extension ext2 = mock(Extension.class); + when(ext2.getUuid()).thenReturn("b"); + when(extensionHelper.getExtensionIdForCluster(anyLong())).thenAnswer(invocation -> invocation.getArguments()[0]); + when(extensionHelper.getExtension(1L)).thenReturn(ext1); + when(extensionHelper.getExtension(2L)).thenReturn(ext2); + List clusters = Arrays.asList(cluster1, cluster2); + listClustersCmd.updateClustersExtensions(clusters); + verify(cluster1).setExtensionId("a"); + verify(cluster2).setExtensionId("b"); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmdTest.java new file mode 100644 index 000000000000..51b1cd9e14b7 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/config/UpdateCfgCmdTest.java @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.config; + +import org.apache.cloudstack.api.response.ConfigurationResponse; +import org.apache.cloudstack.config.Configuration; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import com.cloud.utils.crypt.DBEncryptionUtil; + +@RunWith(MockitoJUnitRunner.class) +public class UpdateCfgCmdTest { + + private UpdateCfgCmd updateCfgCmd; + + private MockedStatic mockedStatic; + + @Before + public void setUp() { + updateCfgCmd = new UpdateCfgCmd(); + mockedStatic = Mockito.mockStatic(DBEncryptionUtil.class); + } + + @After + public void tearDown() { + mockedStatic.close(); + } + + @Test + public void setResponseValueSetsEncryptedValueWhenConfigurationIsEncrypted() { + ConfigurationResponse response = new ConfigurationResponse(); + Configuration cfg = Mockito.mock(Configuration.class); + Mockito.when(cfg.isEncrypted()).thenReturn(true); + Mockito.when(cfg.getValue()).thenReturn("testValue"); + Mockito.when(DBEncryptionUtil.encrypt("testValue")).thenReturn("encryptedValue"); + updateCfgCmd.setResponseValue(response, cfg); + Assert.assertEquals("encryptedValue", response.getValue()); + } + + @Test + public void setResponseValueSetsPlainValueWhenConfigurationIsNotEncrypted() { + ConfigurationResponse response = new ConfigurationResponse(); + Configuration cfg = Mockito.mock(Configuration.class); + Mockito.when(cfg.isEncrypted()).thenReturn(false); + Mockito.when(cfg.getValue()).thenReturn("testValue"); + updateCfgCmd.setResponseValue(response, cfg); + Assert.assertEquals("testValue", response.getValue()); + } + + @Test + public void setResponseValueHandlesNullConfigurationValueGracefully() { + ConfigurationResponse response = new ConfigurationResponse(); + Configuration cfg = Mockito.mock(Configuration.class); + Mockito.when(cfg.isEncrypted()).thenReturn(false); + Mockito.when(cfg.getValue()).thenReturn(null); + updateCfgCmd.setResponseValue(response, cfg); + Assert.assertNull(response.getValue()); + } + +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmdTest.java new file mode 100644 index 000000000000..be21384109c2 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuCardCmdTest.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class CreateGpuCardCmdTest { + + @Test + public void getDeviceId() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertNull(cmd.getDeviceId()); + String deviceId = "0000:00:1f.6"; + ReflectionTestUtils.setField(cmd, "deviceId", deviceId); + assertEquals(deviceId, cmd.getDeviceId()); + } + + @Test + public void getDeviceName() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertNull(cmd.getDeviceName()); + String deviceName = "NVIDIA GeForce GTX 1080"; + ReflectionTestUtils.setField(cmd, "deviceName", deviceName); + assertEquals(deviceName, cmd.getDeviceName()); + } + + @Test + public void getName() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertNull(cmd.getName()); + String name = "Test GPU Card"; + ReflectionTestUtils.setField(cmd, "name", name); + assertEquals(name, cmd.getName()); + } + + @Test + public void getVendorName() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertNull(cmd.getVendorName()); + String vendorName = "NVIDIA"; + ReflectionTestUtils.setField(cmd, "vendorName", vendorName); + assertEquals(vendorName, cmd.getVendorName()); + } + + @Test + public void getVendorId() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertNull(cmd.getVendorId()); + String vendorId = "10de"; // NVIDIA vendor ID + ReflectionTestUtils.setField(cmd, "vendorId", vendorId); + assertEquals(vendorId, cmd.getVendorId()); + } + + @Test + public void getVideoRam() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertNull(cmd.getVideoRam()); + Long videoRam = 8192L; // 8 GB + ReflectionTestUtils.setField(cmd, "videoRam", videoRam); + assertEquals(videoRam, cmd.getVideoRam()); + } + + @Test + public void getEntityOwnerId() { + CreateGpuCardCmd cmd = new CreateGpuCardCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuDeviceCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuDeviceCmdTest.java new file mode 100644 index 000000000000..fd5c568d5bc5 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateGpuDeviceCmdTest.java @@ -0,0 +1,100 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.gpu.GpuDevice; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class CreateGpuDeviceCmdTest { + + @Test + public void getHostId() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertNull(cmd.getHostId()); + Long hostId = 1L; + ReflectionTestUtils.setField(cmd, "hostId", hostId); + assertEquals(hostId, cmd.getHostId()); + } + + @Test + public void getBusAddress() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertNull(cmd.getBusAddress()); + String busAddress = "0000:00:1f.6"; + ReflectionTestUtils.setField(cmd, "busAddress", busAddress); + assertEquals(busAddress, cmd.getBusAddress()); + } + + @Test + public void getGpuCardId() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertNull(cmd.getGpuCardId()); + Long gpuCardId = 1L; + ReflectionTestUtils.setField(cmd, "gpuCardId", gpuCardId); + assertEquals(gpuCardId, cmd.getGpuCardId()); + } + + @Test + public void getVgpuProfileId() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertNull(cmd.getVgpuProfileId()); + Long vgpuProfileId = 1L; + ReflectionTestUtils.setField(cmd, "vgpuProfileId", vgpuProfileId); + assertEquals(vgpuProfileId, cmd.getVgpuProfileId()); + } + + @Test + public void getType() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertEquals(GpuDevice.DeviceType.PCI, cmd.getType()); + String type = "MDEV"; + ReflectionTestUtils.setField(cmd, "type", type); + assertEquals(GpuDevice.DeviceType.MDEV, cmd.getType()); + } + + @Test + public void getParentGpuDeviceId() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertNull(cmd.getParentGpuDeviceId()); + Long parentGpuDeviceId = 1L; + ReflectionTestUtils.setField(cmd, "parentGpuDeviceId", parentGpuDeviceId); + assertEquals(parentGpuDeviceId, cmd.getParentGpuDeviceId()); + } + + @Test + public void getNumaNode() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertEquals("-1", cmd.getNumaNode()); + String numaNode = "0"; + ReflectionTestUtils.setField(cmd, "numaNode", numaNode); + assertEquals(numaNode, cmd.getNumaNode()); + } + + @Test + public void getEntityOwnerId() { + CreateGpuDeviceCmd cmd = new CreateGpuDeviceCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmdTest.java new file mode 100644 index 000000000000..c71286bda652 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/CreateVgpuProfileCmdTest.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class CreateVgpuProfileCmdTest { + + @Test + public void getName() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getName()); + String name = "Test VGPU Profile"; + ReflectionTestUtils.setField(cmd, "name", name); + assertEquals(name, cmd.getName()); + } + + @Test + public void getDescription() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getDescription()); + String description = "Test VGPU Profile Description"; + ReflectionTestUtils.setField(cmd, "description", description); + assertEquals(description, cmd.getDescription()); + } + + @Test + public void getCardId() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getCardId()); + Long cardId = 1L; + ReflectionTestUtils.setField(cmd, "cardId", cardId); + assertEquals(cardId, cmd.getCardId()); + } + + @Test + public void getMaxVgpuPerPgpu() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getMaxVgpuPerPgpu()); + Long maxVgpuPerPgpu = 8L; + ReflectionTestUtils.setField(cmd, "maxVgpuPerPgpu", maxVgpuPerPgpu); + assertEquals(maxVgpuPerPgpu, cmd.getMaxVgpuPerPgpu()); + } + + @Test + public void getVideoRam() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getVideoRam()); + Long videoRam = 8192L; // 8 GB + ReflectionTestUtils.setField(cmd, "videoRam", videoRam); + assertEquals(videoRam, cmd.getVideoRam()); + } + + @Test + public void getMaxHeads() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getMaxHeads()); + Long maxHeads = 2L; + ReflectionTestUtils.setField(cmd, "maxHeads", maxHeads); + assertEquals(maxHeads, cmd.getMaxHeads()); + } + + @Test + public void getMaxResolutionX() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getMaxResolutionX()); + Long maxResolutionX = 1920L; // 1920 pixels + ReflectionTestUtils.setField(cmd, "maxResolutionX", maxResolutionX); + assertEquals(maxResolutionX, cmd.getMaxResolutionX()); + } + + @Test + public void getMaxResolutionY() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertNull(cmd.getMaxResolutionY()); + Long maxResolutionY = 1080L; // 1080 pixels + ReflectionTestUtils.setField(cmd, "maxResolutionY", maxResolutionY); + assertEquals(maxResolutionY, cmd.getMaxResolutionY()); + } + + @Test + public void getEntityOwnerId() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmdTest.java new file mode 100644 index 000000000000..21df915b4209 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuCardCmdTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DeleteGpuCardCmdTest { + + @Test + public void getId() { + DeleteGpuCardCmd cmd = new DeleteGpuCardCmd(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } + + @Test + public void getEntityOwnerId() { + CreateVgpuProfileCmd cmd = new CreateVgpuProfileCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuDeviceCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuDeviceCmdTest.java new file mode 100644 index 000000000000..02b04dd307a8 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteGpuDeviceCmdTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DeleteGpuDeviceCmdTest { + + @Test + public void getIds() { + DeleteGpuDeviceCmd cmd = new DeleteGpuDeviceCmd(); + assertNull(cmd.getIds()); + List ids = List.of(1L, 2L, 3L); + ReflectionTestUtils.setField(cmd, "ids", ids); + assertEquals(ids, cmd.getIds()); + } + + @Test + public void getEntityOwnerId() { + DeleteGpuDeviceCmd cmd = new DeleteGpuDeviceCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmdTest.java new file mode 100644 index 000000000000..ecd43810e65c --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DeleteVgpuProfileCmdTest.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DeleteVgpuProfileCmdTest { + + @Test + public void getId() { + DeleteVgpuProfileCmd cmd = new DeleteVgpuProfileCmd(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } + + @Test + public void getEntityOwnerId() { + DeleteVgpuProfileCmd cmd = new DeleteVgpuProfileCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmdTest.java new file mode 100644 index 000000000000..8295e06e0d55 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/DiscoverGpuDevicesCmdTest.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class DiscoverGpuDevicesCmdTest { + + @Test + public void getId() { + DiscoverGpuDevicesCmd cmd = new DiscoverGpuDevicesCmd(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmdByAdminTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmdByAdminTest.java new file mode 100644 index 000000000000..200bce769336 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/ListGpuDevicesCmdByAdminTest.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ListGpuDevicesCmdByAdminTest { + + @Test + public void getId() { + ListGpuDevicesCmdByAdmin cmd = new ListGpuDevicesCmdByAdmin(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } + + @Test + public void getHostId() { + ListGpuDevicesCmdByAdmin cmd = new ListGpuDevicesCmdByAdmin(); + assertNull(cmd.getHostId()); + Long hostId = 1L; + ReflectionTestUtils.setField(cmd, "hostId", hostId); + assertEquals(hostId, cmd.getHostId()); + } + + @Test + public void getGpuCardId() { + ListGpuDevicesCmdByAdmin cmd = new ListGpuDevicesCmdByAdmin(); + assertNull(cmd.getGpuCardId()); + Long gpuCardId = 1L; + ReflectionTestUtils.setField(cmd, "gpuCardId", gpuCardId); + assertEquals(gpuCardId, cmd.getGpuCardId()); + } + + @Test + public void getVgpuProfileId() { + ListGpuDevicesCmdByAdmin cmd = new ListGpuDevicesCmdByAdmin(); + assertNull(cmd.getVgpuProfileId()); + Long vgpuProfileId = 1L; + ReflectionTestUtils.setField(cmd, "vgpuProfileId", vgpuProfileId); + assertEquals(vgpuProfileId, cmd.getVgpuProfileId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/ManageGpuDeviceCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/ManageGpuDeviceCmdTest.java new file mode 100644 index 000000000000..ee862409a939 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/ManageGpuDeviceCmdTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class ManageGpuDeviceCmdTest { + + @Test + public void getIds() { + ManageGpuDeviceCmd cmd = new ManageGpuDeviceCmd(); + assertNull(cmd.getIds()); + List ids = List.of(1L, 2L, 3L); + ReflectionTestUtils.setField(cmd, "ids", ids); + assertEquals(ids, cmd.getIds()); + } + + @Test + public void getEntityOwnerId() { + ManageGpuDeviceCmd cmd = new ManageGpuDeviceCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UnmanageGpuDeviceCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UnmanageGpuDeviceCmdTest.java new file mode 100644 index 000000000000..63700d9e908d --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UnmanageGpuDeviceCmdTest.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class UnmanageGpuDeviceCmdTest { + + @Test + public void getIds() { + UnmanageGpuDeviceCmd cmd = new UnmanageGpuDeviceCmd(); + assertNull(cmd.getIds()); + List ids = List.of(1L, 2L, 3L); + ReflectionTestUtils.setField(cmd, "ids", ids); + assertEquals(ids, cmd.getIds()); + } + + @Test + public void getEntityOwnerId() { + UnmanageGpuDeviceCmd cmd = new UnmanageGpuDeviceCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmdTest.java new file mode 100644 index 000000000000..ead7ab9d3d30 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuCardCmdTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class UpdateGpuCardCmdTest { + + @Test + public void getId() { + UpdateGpuCardCmd cmd = new UpdateGpuCardCmd(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } + + @Test + public void getDeviceName() { + UpdateGpuCardCmd cmd = new UpdateGpuCardCmd(); + assertNull(cmd.getDeviceName()); + String deviceName = "GPU-1234"; + ReflectionTestUtils.setField(cmd, "deviceName", deviceName); + assertEquals(deviceName, cmd.getDeviceName()); + } + + @Test + public void getName() { + UpdateGpuCardCmd cmd = new UpdateGpuCardCmd(); + assertNull(cmd.getName()); + String name = "Test GPU Card"; + ReflectionTestUtils.setField(cmd, "name", name); + assertEquals(name, cmd.getName()); + } + + @Test + public void getVendorName() { + UpdateGpuCardCmd cmd = new UpdateGpuCardCmd(); + assertNull(cmd.getVendorName()); + String vendorName = "NVIDIA"; + ReflectionTestUtils.setField(cmd, "vendorName", vendorName); + assertEquals(vendorName, cmd.getVendorName()); + } + + @Test + public void getEntityOwnerId() { + UpdateGpuCardCmd cmd = new UpdateGpuCardCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuDeviceCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuDeviceCmdTest.java new file mode 100644 index 000000000000..6ebec48aa003 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateGpuDeviceCmdTest.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.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.apache.cloudstack.gpu.GpuDevice; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class UpdateGpuDeviceCmdTest { + + @Test + public void getId() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } + + @Test + public void getGpuCardId() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertNull(cmd.getGpuCardId()); + Long gpuCardId = 1L; + ReflectionTestUtils.setField(cmd, "gpuCardId", gpuCardId); + assertEquals(gpuCardId, cmd.getGpuCardId()); + } + + @Test + public void getVgpuProfileId() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertNull(cmd.getVgpuProfileId()); + Long vgpuProfileId = 1L; + ReflectionTestUtils.setField(cmd, "vgpuProfileId", vgpuProfileId); + assertEquals(vgpuProfileId, cmd.getVgpuProfileId()); + } + + @Test + public void getType() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertNull(cmd.getType()); + String type = "MDEV"; + ReflectionTestUtils.setField(cmd, "type", type); + assertEquals(GpuDevice.DeviceType.MDEV, cmd.getType()); + } + + @Test + public void getParentGpuDeviceId() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertNull(cmd.getParentGpuDeviceId()); + Long parentGpuDeviceId = 1L; + ReflectionTestUtils.setField(cmd, "parentGpuDeviceId", parentGpuDeviceId); + assertEquals(parentGpuDeviceId, cmd.getParentGpuDeviceId()); + } + + @Test + public void getNumaNode() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertNull(cmd.getNumaNode()); + String numaNode = "0"; + ReflectionTestUtils.setField(cmd, "numaNode", numaNode); + assertEquals(numaNode, cmd.getNumaNode()); + } + + @Test + public void getEntityOwnerId() { + UpdateGpuDeviceCmd cmd = new UpdateGpuDeviceCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmdTest.java new file mode 100644 index 000000000000..95acd71096e9 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/gpu/UpdateVgpuProfileCmdTest.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.gpu; + +import com.cloud.user.Account; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class UpdateVgpuProfileCmdTest { + + @Test + public void getId() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + assertEquals(id, cmd.getId()); + } + + @Test + public void getProfileName() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getProfileName()); + String profileName = "Test VGPU Profile"; + ReflectionTestUtils.setField(cmd, "profileName", profileName); + assertEquals(profileName, cmd.getProfileName()); + } + + @Test + public void getDescription() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getDescription()); + String description = "Test VGPU Profile Description"; + ReflectionTestUtils.setField(cmd, "description", description); + assertEquals(description, cmd.getDescription()); + } + + @Test + public void getMaxVgpuPerPgpu() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getMaxVgpuPerPgpu()); + Long maxVgpuPerPgpu = 8L; + ReflectionTestUtils.setField(cmd, "maxVgpuPerPgpu", maxVgpuPerPgpu); + assertEquals(maxVgpuPerPgpu, cmd.getMaxVgpuPerPgpu()); + } + + @Test + public void getVideoRam() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getVideoRam()); + Long videoRam = 8192L; // 8 GB + ReflectionTestUtils.setField(cmd, "videoRam", videoRam); + assertEquals(videoRam, cmd.getVideoRam()); + } + + @Test + public void getMaxHeads() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getMaxHeads()); + Long maxHeads = 2L; + ReflectionTestUtils.setField(cmd, "maxHeads", maxHeads); + assertEquals(maxHeads, cmd.getMaxHeads()); + } + + @Test + public void getMaxResolutionX() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getMaxResolutionX()); + Long maxResolutionX = 1920L; // Example resolution + ReflectionTestUtils.setField(cmd, "maxResolutionX", maxResolutionX); + assertEquals(maxResolutionX, cmd.getMaxResolutionX()); + } + + @Test + public void getMaxResolutionY() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertNull(cmd.getMaxResolutionY()); + Long maxResolutionY = 1080L; // Example resolution + ReflectionTestUtils.setField(cmd, "maxResolutionY", maxResolutionY); + assertEquals(maxResolutionY, cmd.getMaxResolutionY()); + } + + @Test + public void getEntityOwnerId() { + UpdateVgpuProfileCmd cmd = new UpdateVgpuProfileCmd(); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmdTest.java new file mode 100644 index 000000000000..32230056b6aa --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/AddGuestOsCategoryCmdTest.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.guest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(MockitoJUnitRunner.class) +public class AddGuestOsCategoryCmdTest { + + @Test + public void testGetArch() { + AddGuestOsCategoryCmd cmd = new AddGuestOsCategoryCmd(); + Assert.assertNull(cmd.getName()); + String name = "Name"; + ReflectionTestUtils.setField(cmd, "name", name); + Assert.assertEquals(name, cmd.getName()); + } + + @Test + public void testIsFeatured() { + AddGuestOsCategoryCmd cmd = new AddGuestOsCategoryCmd(); + Assert.assertFalse(cmd.isFeatured()); + ReflectionTestUtils.setField(cmd, "featured", false); + Assert.assertFalse(cmd.isFeatured()); + ReflectionTestUtils.setField(cmd, "featured", true); + Assert.assertTrue(cmd.isFeatured()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmdTest.java new file mode 100644 index 000000000000..3f6a943b3fe9 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCategoryCmdTest.java @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.guest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(MockitoJUnitRunner.class) +public class UpdateGuestOsCategoryCmdTest { + + @Test + public void testGetArch() { + UpdateGuestOsCategoryCmd cmd = new UpdateGuestOsCategoryCmd(); + Assert.assertNull(cmd.getName()); + String name = "Name"; + ReflectionTestUtils.setField(cmd, "name", name); + Assert.assertEquals(name, cmd.getName()); + } + + @Test + public void testIsFeatured() { + UpdateGuestOsCategoryCmd cmd = new UpdateGuestOsCategoryCmd(); + Assert.assertNull(cmd.isFeatured()); + ReflectionTestUtils.setField(cmd, "featured", false); + Assert.assertFalse(cmd.isFeatured()); + ReflectionTestUtils.setField(cmd, "featured", true); + Assert.assertTrue(cmd.isFeatured()); + } + + @Test + public void testGetSortKey() { + UpdateGuestOsCategoryCmd cmd = new UpdateGuestOsCategoryCmd(); + Assert.assertNull(cmd.getSortKey()); + Integer sortKey = 100; + ReflectionTestUtils.setField(cmd, "sortKey", sortKey); + Assert.assertEquals(sortKey, cmd.getSortKey()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmdTest.java new file mode 100644 index 000000000000..f03a791776ed --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/guest/UpdateGuestOsCmdTest.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.admin.guest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(MockitoJUnitRunner.class) +public class UpdateGuestOsCmdTest { + + + @Test + public void testGetZoneId() { + UpdateGuestOsCmd cmd = new UpdateGuestOsCmd(); + Assert.assertNull(cmd.getOsCategoryId()); + Long osCategoryId = 100L; + ReflectionTestUtils.setField(cmd, "osCategoryId", osCategoryId); + Assert.assertEquals(osCategoryId, cmd.getOsCategoryId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmdTest.java new file mode 100644 index 000000000000..096395b1359e --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CloneNetworkOfferingCmdTest.java @@ -0,0 +1,324 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.network; + +import com.cloud.offering.NetworkOffering; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.NetworkOfferingResponse; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class CloneNetworkOfferingCmdTest { + + private CloneNetworkOfferingCmd cloneNetworkOfferingCmd; + + @Mock + private com.cloud.configuration.ConfigurationService configService; + + @Mock + private ResponseGenerator responseGenerator; + + @Mock + private NetworkOffering mockNetworkOffering; + + @Mock + private NetworkOfferingResponse mockNetworkOfferingResponse; + + @Before + public void setUp() { + cloneNetworkOfferingCmd = new CloneNetworkOfferingCmd(); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "_configService", configService); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "_responseGenerator", responseGenerator); + } + + @Test + public void testGetSourceOfferingId() { + Long sourceOfferingId = 123L; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", sourceOfferingId); + assertEquals(sourceOfferingId, cloneNetworkOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testGetAddServices() { + List addServices = Arrays.asList("Dhcp", "Dns"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "addServices", addServices); + assertEquals(addServices, cloneNetworkOfferingCmd.getAddServices()); + } + + @Test + public void testGetDropServices() { + List dropServices = Arrays.asList("Firewall", "Vpn"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "dropServices", dropServices); + assertEquals(dropServices, cloneNetworkOfferingCmd.getDropServices()); + } + + @Test + public void testGetGuestIpType() { + String guestIpType = "Isolated"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "guestIptype", guestIpType); + assertEquals(guestIpType, cloneNetworkOfferingCmd.getGuestIpType()); + } + + @Test + public void testGetTraffictype() { + String trafficType = "GUEST"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "traffictype", trafficType); + assertEquals(trafficType, cloneNetworkOfferingCmd.getTraffictype()); + } + + @Test + public void testGetName() { + String name = "ClonedNetworkOffering"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkOfferingName", name); + assertEquals(name, cloneNetworkOfferingCmd.getNetworkOfferingName()); + } + + @Test + public void testGetDisplayText() { + String displayText = "Cloned Network Offering Display Text"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "displayText", displayText); + assertEquals(displayText, cloneNetworkOfferingCmd.getDisplayText()); + } + + @Test + public void testGetDisplayTextDefaultsToName() { + String name = "ClonedNetworkOffering"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkOfferingName", name); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "displayText", null); + assertEquals(name, cloneNetworkOfferingCmd.getDisplayText()); + } + + @Test + public void testGetAvailability() { + String availability = "Required"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "availability", availability); + assertEquals(availability, cloneNetworkOfferingCmd.getAvailability()); + } + + @Test + public void testGetTags() { + String tags = "tag1,tag2,tag3"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "tags", tags); + assertEquals(tags, cloneNetworkOfferingCmd.getTags()); + } + + @Test + public void testExecuteSuccess() { + Long sourceOfferingId = 123L; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneNetworkOffering(any(CloneNetworkOfferingCmd.class))).thenReturn(mockNetworkOffering); + when(responseGenerator.createNetworkOfferingResponse(mockNetworkOffering)).thenReturn(mockNetworkOfferingResponse); + + cloneNetworkOfferingCmd.execute(); + + assertNotNull(cloneNetworkOfferingCmd.getResponseObject()); + assertEquals(mockNetworkOfferingResponse, cloneNetworkOfferingCmd.getResponseObject()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteFailure() { + Long sourceOfferingId = 123L; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneNetworkOffering(any(CloneNetworkOfferingCmd.class))).thenReturn(null); + + try { + cloneNetworkOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + assertEquals("Failed to clone network offering", e.getMessage()); + throw e; + } + } + + @Test + public void testGetConserveMode() { + Boolean conserveMode = true; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "conserveMode", conserveMode); + assertEquals(conserveMode, cloneNetworkOfferingCmd.getConserveMode()); + } + + @Test + public void testGetSpecifyVlan() { + Boolean specifyVlan = false; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "specifyVlan", specifyVlan); + assertEquals(specifyVlan, cloneNetworkOfferingCmd.getSpecifyVlan()); + } + + @Test + public void testGetSpecifyIpRanges() { + Boolean specifyIpRanges = true; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "specifyIpRanges", specifyIpRanges); + assertEquals(specifyIpRanges, cloneNetworkOfferingCmd.getSpecifyIpRanges()); + } + + @Test + public void testGetIsPersistent() { + Boolean isPersistent = true; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "isPersistent", isPersistent); + assertEquals(isPersistent, cloneNetworkOfferingCmd.getIsPersistent()); + } + + @Test + public void testGetEgressDefaultPolicy() { + Boolean egressDefaultPolicy = false; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "egressDefaultPolicy", egressDefaultPolicy); + assertEquals(egressDefaultPolicy, cloneNetworkOfferingCmd.getEgressDefaultPolicy()); + } + + @Test + public void testGetServiceOfferingId() { + Long serviceOfferingId = 456L; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "serviceOfferingId", serviceOfferingId); + assertEquals(serviceOfferingId, cloneNetworkOfferingCmd.getServiceOfferingId()); + } + + @Test + public void testGetForVpc() { + Boolean forVpc = true; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "forVpc", forVpc); + assertEquals(forVpc, cloneNetworkOfferingCmd.getForVpc()); + } + + @Test + public void testGetMaxConnections() { + Integer maxConnections = 1000; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "maxConnections", maxConnections); + assertEquals(maxConnections, cloneNetworkOfferingCmd.getMaxconnections()); + } + + @Test + public void testGetNetworkRate() { + Integer networkRate = 200; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkRate", networkRate); + assertEquals(networkRate, cloneNetworkOfferingCmd.getNetworkRate()); + } + + @Test + public void testGetInternetProtocol() { + String internetProtocol = "ipv4"; + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "internetProtocol", internetProtocol); + assertEquals(internetProtocol, cloneNetworkOfferingCmd.getInternetProtocol()); + } + + @Test + public void testAddServicesNullByDefault() { + assertNull(cloneNetworkOfferingCmd.getAddServices()); + } + + @Test + public void testDropServicesNullByDefault() { + assertNull(cloneNetworkOfferingCmd.getDropServices()); + } + + @Test + public void testSupportedServicesParameter() { + List supportedServices = Arrays.asList("Dhcp", "Dns", "SourceNat"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "supportedServices", supportedServices); + assertEquals(supportedServices, cloneNetworkOfferingCmd.getSupportedServices()); + } + + @Test + public void testServiceProviderListParameter() { + Map> serviceProviderList = new HashMap<>(); + + HashMap dhcpProvider = new HashMap<>(); + dhcpProvider.put("service", "Dhcp"); + dhcpProvider.put("provider", "VirtualRouter"); + + HashMap dnsProvider = new HashMap<>(); + dnsProvider.put("service", "Dns"); + dnsProvider.put("provider", "VirtualRouter"); + + serviceProviderList.put("0", dhcpProvider); + serviceProviderList.put("1", dnsProvider); + + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "serviceProviderList", serviceProviderList); + + Map> result = cloneNetworkOfferingCmd.getServiceProviders(); + assertNotNull(result); + assertEquals(2, result.size()); + assertNotNull(result.get("Dhcp")); + assertNotNull(result.get("Dns")); + assertEquals("VirtualRouter", result.get("Dhcp").get(0)); + assertEquals("VirtualRouter", result.get("Dns").get(0)); + } + + @Test + public void testCloneWithAllParameters() { + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", 123L); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkOfferingName", "ClonedOffering"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "displayText", "Cloned Offering Display"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "availability", "Optional"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "guestIptype", "Isolated"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "traffictype", "GUEST"); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "conserveMode", true); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "specifyVlan", false); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "isPersistent", true); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "egressDefaultPolicy", false); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "networkRate", 200); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "serviceOfferingId", 456L); + + assertEquals(Long.valueOf(123L), cloneNetworkOfferingCmd.getSourceOfferingId()); + assertEquals("ClonedOffering", cloneNetworkOfferingCmd.getNetworkOfferingName()); + assertEquals("Cloned Offering Display", cloneNetworkOfferingCmd.getDisplayText()); + assertEquals("Optional", cloneNetworkOfferingCmd.getAvailability()); + assertEquals("Isolated", cloneNetworkOfferingCmd.getGuestIpType()); + assertEquals("GUEST", cloneNetworkOfferingCmd.getTraffictype()); + assertEquals(Boolean.TRUE, cloneNetworkOfferingCmd.getConserveMode()); + assertEquals(Boolean.FALSE, cloneNetworkOfferingCmd.getSpecifyVlan()); + assertEquals(Boolean.TRUE, cloneNetworkOfferingCmd.getIsPersistent()); + assertEquals(Boolean.FALSE, cloneNetworkOfferingCmd.getEgressDefaultPolicy()); + assertEquals(Integer.valueOf(200), cloneNetworkOfferingCmd.getNetworkRate()); + assertEquals(Long.valueOf(456L), cloneNetworkOfferingCmd.getServiceOfferingId()); + } + + @Test + public void testCloneWithAddAndDropServices() { + List addServices = Arrays.asList("StaticNat", "PortForwarding"); + List dropServices = Arrays.asList("Vpn"); + + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "sourceOfferingId", 123L); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "addServices", addServices); + ReflectionTestUtils.setField(cloneNetworkOfferingCmd, "dropServices", dropServices); + + assertEquals(addServices, cloneNetworkOfferingCmd.getAddServices()); + assertEquals(dropServices, cloneNetworkOfferingCmd.getDropServices()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmdTest.java index e1393e316993..38d0df2e8b82 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForGuestNetworkCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.Ipv4SubnetForGuestNetworkResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.Ipv4GuestSubnetNetworkMap; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -29,6 +30,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class CreateIpv4SubnetForGuestNetworkCmdTest { @@ -37,6 +40,7 @@ public class CreateIpv4SubnetForGuestNetworkCmdTest { @Test public void testCreateIpv4SubnetForGuestNetworkCmd() { Long parentId = 1L; + String parentUuid = UUID.randomUUID().toString(); String subnet = "192.168.1.0/24"; Integer cidrSize = 26; @@ -46,12 +50,14 @@ public void testCreateIpv4SubnetForGuestNetworkCmd() { ReflectionTestUtils.setField(cmd, "cidrSize", cidrSize); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("parentid", parentUuid); + Assert.assertEquals(parentId, cmd.getParentId()); Assert.assertEquals(subnet, cmd.getSubnet()); Assert.assertEquals(cidrSize, cmd.getCidrSize()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_IP4_GUEST_SUBNET_CREATE, cmd.getEventType()); - Assert.assertEquals(String.format("Creating guest IPv4 subnet %s in zone subnet=%s", subnet, parentId), cmd.getEventDescription()); + Assert.assertEquals(String.format("Creating guest IPv4 subnet %s in zone subnet: %s", subnet, parentUuid), cmd.getEventDescription()); Ipv4GuestSubnetNetworkMap ipv4GuestSubnetNetworkMap = Mockito.mock(Ipv4GuestSubnetNetworkMap.class); Mockito.when(routedIpv4Manager.createIpv4SubnetForGuestNetwork(cmd)).thenReturn(ipv4GuestSubnetNetworkMap); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmdTest.java index 51c1eb986c47..560ecdc3b296 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/CreateIpv4SubnetForZoneCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.DataCenterIpv4SubnetResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -29,6 +30,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class CreateIpv4SubnetForZoneCmdTest { @@ -37,6 +40,7 @@ public class CreateIpv4SubnetForZoneCmdTest { @Test public void testCreateIpv4SubnetForZoneCmd() { Long zoneId = 1L; + String zoneUuid = UUID.randomUUID().toString(); String subnet = "192.168.1.0/24"; String accountName = "user"; Long projectId = 10L; @@ -50,6 +54,8 @@ public void testCreateIpv4SubnetForZoneCmd() { ReflectionTestUtils.setField(cmd,"domainId", domainId); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("zoneid", zoneUuid); + Assert.assertEquals(zoneId, cmd.getZoneId()); Assert.assertEquals(subnet, cmd.getSubnet()); Assert.assertEquals(accountName, cmd.getAccountName()); @@ -57,7 +63,7 @@ public void testCreateIpv4SubnetForZoneCmd() { Assert.assertEquals(domainId, cmd.getDomainId()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_ZONE_IP4_SUBNET_CREATE, cmd.getEventType()); - Assert.assertEquals(String.format("Creating guest IPv4 subnet %s for zone=%s", subnet, zoneId), cmd.getEventDescription()); + Assert.assertEquals(String.format("Creating guest IPv4 subnet %s for zone: %s", subnet, zoneUuid), cmd.getEventDescription()); DataCenterIpv4GuestSubnet zoneSubnet = Mockito.mock(DataCenterIpv4GuestSubnet.class); Mockito.when(routedIpv4Manager.createDataCenterIpv4GuestSubnet(cmd)).thenReturn(zoneSubnet); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmdTest.java index 7db77098b233..4640510ccdab 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DedicateIpv4SubnetForZoneCmdTest.java @@ -19,6 +19,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.DataCenterIpv4SubnetResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class DedicateIpv4SubnetForZoneCmdTest { @@ -36,6 +39,7 @@ public class DedicateIpv4SubnetForZoneCmdTest { @Test public void testDedicateIpv4SubnetForZoneCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); String accountName = "user"; Long projectId = 10L; Long domainId = 11L; @@ -47,6 +51,8 @@ public void testDedicateIpv4SubnetForZoneCmd() { ReflectionTestUtils.setField(cmd,"domainId", domainId); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(accountName, cmd.getAccountName()); Assert.assertEquals(projectId, cmd.getProjectId()); @@ -54,7 +60,7 @@ public void testDedicateIpv4SubnetForZoneCmd() { Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_ZONE_IP4_SUBNET_DEDICATE, cmd.getEventType()); - Assert.assertEquals(String.format("Dedicating zone IPv4 subnet %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Dedicating zone's IPv4 subnet with ID: %s", uuid), cmd.getEventDescription()); DataCenterIpv4GuestSubnet zoneSubnet = Mockito.mock(DataCenterIpv4GuestSubnet.class); Mockito.when(routedIpv4Manager.dedicateDataCenterIpv4GuestSubnet(cmd)).thenReturn(zoneSubnet); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmdTest.java index a4af5ddf748f..cd25d8d24014 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForGuestNetworkCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; import org.junit.Test; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class DeleteIpv4SubnetForGuestNetworkCmdTest { @@ -36,15 +39,18 @@ public class DeleteIpv4SubnetForGuestNetworkCmdTest { @Test public void testDeleteIpv4SubnetForGuestNetworkCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); DeleteIpv4SubnetForGuestNetworkCmd cmd = new DeleteIpv4SubnetForGuestNetworkCmd(); ReflectionTestUtils.setField(cmd, "id", id); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_IP4_GUEST_SUBNET_DELETE, cmd.getEventType()); - Assert.assertEquals(String.format("Deleting guest IPv4 subnet %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Deleting guest IPv4 subnet with ID: %s", uuid), cmd.getEventDescription()); Mockito.when(routedIpv4Manager.deleteIpv4SubnetForGuestNetwork(cmd)).thenReturn(true); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmdTest.java index 7af173f09d96..269fb3f3c192 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/DeleteIpv4SubnetForZoneCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; import org.junit.Test; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class DeleteIpv4SubnetForZoneCmdTest { @@ -36,15 +39,18 @@ public class DeleteIpv4SubnetForZoneCmdTest { @Test public void testDeleteIpv4SubnetForZoneCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); DeleteIpv4SubnetForZoneCmd cmd = new DeleteIpv4SubnetForZoneCmd(); ReflectionTestUtils.setField(cmd, "id", id); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_ZONE_IP4_SUBNET_DELETE, cmd.getEventType()); - Assert.assertEquals(String.format("Deleting zone IPv4 subnet %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Deleting zone IPv4 subnet with ID: %s", uuid), cmd.getEventDescription()); Mockito.when(routedIpv4Manager.deleteDataCenterIpv4GuestSubnet(cmd)).thenReturn(true); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmdTest.java index 9ce9a4f9464f..6e5d2a95f3ab 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/ReleaseDedicatedIpv4SubnetForZoneCmdTest.java @@ -19,6 +19,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.DataCenterIpv4SubnetResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class ReleaseDedicatedIpv4SubnetForZoneCmdTest { @@ -36,15 +39,18 @@ public class ReleaseDedicatedIpv4SubnetForZoneCmdTest { @Test public void testReleaseDedicatedIpv4SubnetForZoneCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); ReleaseDedicatedIpv4SubnetForZoneCmd cmd = new ReleaseDedicatedIpv4SubnetForZoneCmd(); ReflectionTestUtils.setField(cmd, "id", id); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_ZONE_IP4_SUBNET_RELEASE, cmd.getEventType()); - Assert.assertEquals(String.format("Releasing a dedicated zone IPv4 subnet %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Releasing dedicated zone IPv4 subnet with ID: %s", uuid), cmd.getEventDescription()); DataCenterIpv4GuestSubnet zoneSubnet = Mockito.mock(DataCenterIpv4GuestSubnet.class); Mockito.when(routedIpv4Manager.releaseDedicatedDataCenterIpv4GuestSubnet(cmd)).thenReturn(zoneSubnet); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmdTest.java index cdb9cce22d83..af37006eafd2 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/UpdateIpv4SubnetForZoneCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.DataCenterIpv4SubnetResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.datacenter.DataCenterIpv4GuestSubnet; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -29,6 +30,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class UpdateIpv4SubnetForZoneCmdTest { @@ -37,6 +40,7 @@ public class UpdateIpv4SubnetForZoneCmdTest { @Test public void testUpdateIpv4SubnetForZoneCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); String subnet = "192.168.1.0/24"; UpdateIpv4SubnetForZoneCmd cmd = new UpdateIpv4SubnetForZoneCmd(); @@ -44,11 +48,13 @@ public void testUpdateIpv4SubnetForZoneCmd() { ReflectionTestUtils.setField(cmd, "subnet", subnet); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(subnet, cmd.getSubnet()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_ZONE_IP4_SUBNET_UPDATE, cmd.getEventType()); - Assert.assertEquals(String.format("Updating zone IPv4 subnet %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Updating zone IPv4 subnet with ID: %s", uuid), cmd.getEventDescription()); DataCenterIpv4GuestSubnet zoneSubnet = Mockito.mock(DataCenterIpv4GuestSubnet.class); Mockito.when(routedIpv4Manager.updateDataCenterIpv4GuestSubnet(cmd)).thenReturn(zoneSubnet); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmdTest.java index 28ddad17afe5..3db1fab466f6 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForNetworkCmdTest.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.response.NetworkResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; import org.junit.Test; @@ -33,6 +34,7 @@ import java.util.Arrays; import java.util.List; +import java.util.UUID; @RunWith(MockitoJUnitRunner.class) public class ChangeBgpPeersForNetworkCmdTest { @@ -44,6 +46,7 @@ public class ChangeBgpPeersForNetworkCmdTest { @Test public void testChangeBgpPeersForNetworkCmd() { Long networkId = 10L; + String networkUuid = UUID.randomUUID().toString(); List bgpPeerIds = Arrays.asList(20L, 21L); ChangeBgpPeersForNetworkCmd cmd = new ChangeBgpPeersForNetworkCmd(); @@ -52,11 +55,13 @@ public void testChangeBgpPeersForNetworkCmd() { ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); ReflectionTestUtils.setField(cmd,"_responseGenerator", _responseGenerator); + CallContext.current().putApiResourceUuid("networkid", networkUuid); + Assert.assertEquals(networkId, cmd.getNetworkId()); Assert.assertEquals(bgpPeerIds, cmd.getBgpPeerIds()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_NETWORK_BGP_PEER_UPDATE, cmd.getEventType()); - Assert.assertEquals(String.format("Changing Bgp Peers for network %s", networkId), cmd.getEventDescription()); + Assert.assertEquals(String.format("Changing BGP Peers for Network with ID: %s", networkUuid), cmd.getEventDescription()); Network network = Mockito.mock(Network.class); Mockito.when(routedIpv4Manager.changeBgpPeersForNetwork(cmd)).thenReturn(network); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmdTest.java index 96eb1f020de2..fb85f7060684 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ChangeBgpPeersForVpcCmdTest.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.response.VpcResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; import org.junit.Test; @@ -33,6 +34,7 @@ import java.util.Arrays; import java.util.List; +import java.util.UUID; @RunWith(MockitoJUnitRunner.class) public class ChangeBgpPeersForVpcCmdTest { @@ -44,6 +46,7 @@ public class ChangeBgpPeersForVpcCmdTest { @Test public void testChangeBgpPeersForVpcCmd() { Long VpcId = 10L; + String vpcUuid = UUID.randomUUID().toString(); List bgpPeerIds = Arrays.asList(20L, 21L); ChangeBgpPeersForVpcCmd cmd = new ChangeBgpPeersForVpcCmd(); @@ -52,11 +55,13 @@ public void testChangeBgpPeersForVpcCmd() { ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); ReflectionTestUtils.setField(cmd,"_responseGenerator", _responseGenerator); + CallContext.current().putApiResourceUuid("vpcid", vpcUuid); + Assert.assertEquals(VpcId, cmd.getVpcId()); Assert.assertEquals(bgpPeerIds, cmd.getBgpPeerIds()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_VPC_BGP_PEER_UPDATE, cmd.getEventType()); - Assert.assertEquals(String.format("Changing Bgp Peers for VPC %s", VpcId), cmd.getEventDescription()); + Assert.assertEquals(String.format("Changing BGP Peers for VPC with ID: %s", vpcUuid), cmd.getEventDescription()); Vpc Vpc = Mockito.mock(Vpc.class); Mockito.when(routedIpv4Manager.changeBgpPeersForVpc(cmd)).thenReturn(Vpc); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmdTest.java index 0d802bf36199..3abc5f57d017 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/CreateBgpPeerCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.BgpPeerResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.BgpPeer; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -29,6 +30,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class CreateBgpPeerCmdTest { @@ -37,6 +40,7 @@ public class CreateBgpPeerCmdTest { @Test public void testCreateBgpPeerCmd() { Long zoneId = 1L; + String zoneUuid = UUID.randomUUID().toString(); String accountName = "user"; Long projectId = 10L; Long domainId = 11L; @@ -56,6 +60,8 @@ public void testCreateBgpPeerCmd() { ReflectionTestUtils.setField(cmd,"domainId", domainId); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("zoneid", zoneUuid); + Assert.assertEquals(zoneId, cmd.getZoneId()); Assert.assertEquals(ip4Address, cmd.getIp4Address()); Assert.assertEquals(ip6Address, cmd.getIp6Address()); @@ -67,7 +73,7 @@ public void testCreateBgpPeerCmd() { Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_BGP_PEER_CREATE, cmd.getEventType()); - Assert.assertEquals(String.format("Creating Bgp Peer %s for zone=%s", peerAsNumber, zoneId), cmd.getEventDescription()); + Assert.assertEquals(String.format("Creating BGP Peer %s for zone with ID: %s", peerAsNumber, zoneUuid), cmd.getEventDescription()); BgpPeer bgpPeer = Mockito.mock(BgpPeer.class); Mockito.when(routedIpv4Manager.createBgpPeer(cmd)).thenReturn(bgpPeer); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmdTest.java index f3ae007da285..c5edb1b8f53f 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DedicateBgpPeerCmdTest.java @@ -19,6 +19,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.BgpPeerResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.BgpPeer; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class DedicateBgpPeerCmdTest { @@ -36,6 +39,7 @@ public class DedicateBgpPeerCmdTest { @Test public void testDedicateBgpPeerCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); String accountName = "user"; Long projectId = 10L; Long domainId = 11L; @@ -47,6 +51,8 @@ public void testDedicateBgpPeerCmd() { ReflectionTestUtils.setField(cmd,"domainId", domainId); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(accountName, cmd.getAccountName()); Assert.assertEquals(projectId, cmd.getProjectId()); @@ -54,7 +60,7 @@ public void testDedicateBgpPeerCmd() { Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_BGP_PEER_DEDICATE, cmd.getEventType()); - Assert.assertEquals(String.format("Dedicating Bgp Peer %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Dedicating BGP Peer with ID: %s", uuid), cmd.getEventDescription()); BgpPeer bgpPeer = Mockito.mock(BgpPeer.class); Mockito.when(routedIpv4Manager.dedicateBgpPeer(cmd)).thenReturn(bgpPeer); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmdTest.java index 5e747188fda3..5228a63dc92d 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/DeleteBgpPeerCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; import org.junit.Test; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class DeleteBgpPeerCmdTest { @@ -36,15 +39,18 @@ public class DeleteBgpPeerCmdTest { @Test public void testDeleteBgpPeerCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); DeleteBgpPeerCmd cmd = new DeleteBgpPeerCmd(); ReflectionTestUtils.setField(cmd, "id", id); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_BGP_PEER_DELETE, cmd.getEventType()); - Assert.assertEquals(String.format("Deleting Bgp Peer %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Deleting BGP Peer with ID: %s", uuid), cmd.getEventDescription()); Mockito.when(routedIpv4Manager.deleteBgpPeer(cmd)).thenReturn(true); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmdTest.java index 8c55c4a73479..60a814d63050 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/ReleaseDedicatedBgpPeerCmdTest.java @@ -19,6 +19,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.BgpPeerResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.BgpPeer; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -28,6 +29,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class ReleaseDedicatedBgpPeerCmdTest { @@ -36,15 +39,18 @@ public class ReleaseDedicatedBgpPeerCmdTest { @Test public void testReleaseDedicatedBgpPeerCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); ReleaseDedicatedBgpPeerCmd cmd = new ReleaseDedicatedBgpPeerCmd(); ReflectionTestUtils.setField(cmd, "id", id); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_BGP_PEER_RELEASE, cmd.getEventType()); - Assert.assertEquals(String.format("Releasing a dedicated Bgp Peer %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Releasing dedicated BGP Peer with ID: %s", uuid), cmd.getEventDescription()); BgpPeer bgpPeer = Mockito.mock(BgpPeer.class); Mockito.when(routedIpv4Manager.releaseDedicatedBgpPeer(cmd)).thenReturn(bgpPeer); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmdTest.java index 003944c61474..d594bc5718b7 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/network/bgp/UpdateBgpPeerCmdTest.java @@ -20,6 +20,7 @@ import com.cloud.event.EventTypes; import org.apache.cloudstack.api.response.BgpPeerResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.BgpPeer; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; @@ -29,6 +30,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class UpdateBgpPeerCmdTest { @@ -37,6 +40,7 @@ public class UpdateBgpPeerCmdTest { @Test public void testUpdateBgpPeerCmd() { Long id = 1L; + String uuid = UUID.randomUUID().toString(); String ip4Address = "ip4-address"; String ip6Address = "ip6-address"; Long peerAsNumber = 15000L; @@ -50,6 +54,8 @@ public void testUpdateBgpPeerCmd() { ReflectionTestUtils.setField(cmd, "password", peerPassword); ReflectionTestUtils.setField(cmd,"routedIpv4Manager", routedIpv4Manager); + CallContext.current().putApiResourceUuid("id", uuid); + Assert.assertEquals(id, cmd.getId()); Assert.assertEquals(ip4Address, cmd.getIp4Address()); Assert.assertEquals(ip6Address, cmd.getIp6Address()); @@ -57,7 +63,7 @@ public void testUpdateBgpPeerCmd() { Assert.assertEquals(peerPassword, cmd.getPassword()); Assert.assertEquals(1L, cmd.getEntityOwnerId()); Assert.assertEquals(EventTypes.EVENT_BGP_PEER_UPDATE, cmd.getEventType()); - Assert.assertEquals(String.format("Updating Bgp Peer %s", id), cmd.getEventDescription()); + Assert.assertEquals(String.format("Updating BGP Peer with ID: %s", uuid), cmd.getEventDescription()); BgpPeer bgpPeer = Mockito.mock(BgpPeer.class); Mockito.when(routedIpv4Manager.updateBgpPeer(cmd)).thenReturn(bgpPeer); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmdTest.java new file mode 100644 index 000000000000..b4f7c55bd1fa --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CloneServiceOfferingCmdTest.java @@ -0,0 +1,669 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.offering; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.offering.ServiceOffering; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.ServiceOfferingResponse; +import org.apache.cloudstack.vm.lease.VMLeaseManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class CloneServiceOfferingCmdTest { + + private CloneServiceOfferingCmd cloneServiceOfferingCmd; + + @Mock + private com.cloud.configuration.ConfigurationService configService; + + @Mock + private ResponseGenerator responseGenerator; + + @Mock + private ServiceOffering mockServiceOffering; + + @Mock + private ServiceOfferingResponse mockServiceOfferingResponse; + + @Before + public void setUp() { + cloneServiceOfferingCmd = new CloneServiceOfferingCmd(); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "_configService", configService); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "_responseGenerator", responseGenerator); + } + + @Test + public void testGetSourceOfferingId() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + assertEquals(sourceOfferingId, cloneServiceOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testGetServiceOfferingName() { + String name = "ClonedServiceOffering"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", name); + assertEquals(name, cloneServiceOfferingCmd.getServiceOfferingName()); + } + + @Test + public void testGetDisplayText() { + String displayText = "Cloned Service Offering Display Text"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", displayText); + assertEquals(displayText, cloneServiceOfferingCmd.getDisplayText()); + } + + @Test + public void testGetDisplayTextDefaultsToName() { + String name = "ClonedServiceOffering"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", name); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", null); + assertEquals(name, cloneServiceOfferingCmd.getDisplayText()); + } + + @Test + public void testGetCpu() { + Integer cpu = 4; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", cpu); + assertEquals(cpu, cloneServiceOfferingCmd.getCpuNumber()); + } + + @Test + public void testGetMemory() { + Integer memory = 8192; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", memory); + assertEquals(memory, cloneServiceOfferingCmd.getMemory()); + } + + @Test + public void testGetCpuSpeed() { + Integer cpuSpeed = 2000; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", cpuSpeed); + assertEquals(cpuSpeed, cloneServiceOfferingCmd.getCpuSpeed()); + } + + @Test + public void testGetOfferHa() { + Boolean offerHa = true; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "offerHa", offerHa); + assertEquals(offerHa, cloneServiceOfferingCmd.isOfferHa()); + } + + @Test + public void testGetLimitCpuUse() { + Boolean limitCpuUse = false; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "limitCpuUse", limitCpuUse); + assertEquals(limitCpuUse, cloneServiceOfferingCmd.isLimitCpuUse()); + } + + @Test + public void testGetVolatileVm() { + Boolean volatileVm = true; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "isVolatile", volatileVm); + assertEquals(volatileVm, cloneServiceOfferingCmd.isVolatileVm()); + } + + @Test + public void testGetStorageType() { + String storageType = "local"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "storageType", storageType); + assertEquals(storageType, cloneServiceOfferingCmd.getStorageType()); + } + + @Test + public void testGetTags() { + String tags = "ssd,premium,dedicated"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "tags", tags); + assertEquals(tags, cloneServiceOfferingCmd.getTags()); + } + + @Test + public void testGetHostTag() { + String hostTag = "gpu-enabled"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "hostTag", hostTag); + assertEquals(hostTag, cloneServiceOfferingCmd.getHostTag()); + } + + @Test + public void testGetNetworkRate() { + Integer networkRate = 1000; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "networkRate", networkRate); + assertEquals(networkRate, cloneServiceOfferingCmd.getNetworkRate()); + } + + @Test + public void testGetDeploymentPlanner() { + String deploymentPlanner = "UserDispersingPlanner"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "deploymentPlanner", deploymentPlanner); + assertEquals(deploymentPlanner, cloneServiceOfferingCmd.getDeploymentPlanner()); + } + + @Test + public void testGetDetails() { + Map> details = new HashMap<>(); + + HashMap cpuOvercommit = new HashMap<>(); + cpuOvercommit.put("key", "cpuOvercommitRatio"); + cpuOvercommit.put("value", "2.0"); + + HashMap memoryOvercommit = new HashMap<>(); + memoryOvercommit.put("key", "memoryOvercommitRatio"); + memoryOvercommit.put("value", "1.5"); + + details.put("0", cpuOvercommit); + details.put("1", memoryOvercommit); + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "details", details); + + Map result = cloneServiceOfferingCmd.getDetails(); + assertNotNull(result); + assertEquals("2.0", result.get("cpuOvercommitRatio")); + assertEquals("1.5", result.get("memoryOvercommitRatio")); + } + + @Test + public void testIsPurgeResources() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", true); + assertTrue(cloneServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testIsPurgeResourcesFalse() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", false); + assertFalse(cloneServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testIsPurgeResourcesDefaultFalse() { + assertFalse(cloneServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testGetLeaseDuration() { + Integer leaseDuration = 3600; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseDuration", leaseDuration); + assertEquals(leaseDuration, cloneServiceOfferingCmd.getLeaseDuration()); + } + + @Test + public void testGetLeaseExpiryAction() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "stop"); + assertEquals(VMLeaseManager.ExpiryAction.STOP, cloneServiceOfferingCmd.getLeaseExpiryAction()); + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "DESTROY"); + assertEquals(VMLeaseManager.ExpiryAction.DESTROY, cloneServiceOfferingCmd.getLeaseExpiryAction()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetLeaseExpiryActionInvalidValue() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "InvalidAction"); + cloneServiceOfferingCmd.getLeaseExpiryAction(); + } + + @Test + public void testGetVgpuProfileId() { + Long vgpuProfileId = 10L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "vgpuProfileId", vgpuProfileId); + assertEquals(vgpuProfileId, cloneServiceOfferingCmd.getVgpuProfileId()); + } + + @Test + public void testGetGpuCount() { + Integer gpuCount = 2; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuCount", gpuCount); + assertEquals(gpuCount, cloneServiceOfferingCmd.getGpuCount()); + } + + @Test + public void testGetGpuDisplay() { + Boolean gpuDisplay = true; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuDisplay", gpuDisplay); + assertEquals(gpuDisplay, cloneServiceOfferingCmd.getGpuDisplay()); + } + + @Test + public void testExecuteSuccess() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteFailure() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(null); + + try { + cloneServiceOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + assertEquals("Failed to clone service offering", e.getMessage()); + } + } + + @Test + public void testExecuteSuccessWithAllParameters() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "ClonedOffering"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", "Test Display"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", 4); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", 8192); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", 2000); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteWithInvalidParameterException() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))) + .thenThrow(new InvalidParameterValueException("Invalid source offering ID")); + + try { + cloneServiceOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.PARAM_ERROR, e.getErrorCode()); + assertEquals("Invalid source offering ID", e.getMessage()); + } + } + + @Test + public void testExecuteWithCloudRuntimeException() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))) + .thenThrow(new com.cloud.utils.exception.CloudRuntimeException("Runtime error during clone")); + + try { + cloneServiceOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + assertEquals("Runtime error during clone", e.getMessage()); + } + } + + @Test + public void testExecuteResponseNameIsSet() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + // Verify that response name would be set (actual verification would require accessing the response object's internal state) + } + + @Test + public void testCloneWithAllParameters() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "ClonedServiceOffering"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", "Cloned Service Offering"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", 4); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", 8192); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", 2000); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "offerHa", true); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "limitCpuUse", false); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "isVolatile", true); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "storageType", "local"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "tags", "premium"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "hostTag", "gpu-enabled"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "networkRate", 1000); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "deploymentPlanner", "UserDispersingPlanner"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", true); + + assertEquals(Long.valueOf(555L), cloneServiceOfferingCmd.getSourceOfferingId()); + assertEquals("ClonedServiceOffering", cloneServiceOfferingCmd.getServiceOfferingName()); + assertEquals("Cloned Service Offering", cloneServiceOfferingCmd.getDisplayText()); + assertEquals(Integer.valueOf(4), cloneServiceOfferingCmd.getCpuNumber()); + assertEquals(Integer.valueOf(8192), cloneServiceOfferingCmd.getMemory()); + assertEquals(Integer.valueOf(2000), cloneServiceOfferingCmd.getCpuSpeed()); + assertEquals(Boolean.TRUE, cloneServiceOfferingCmd.isOfferHa()); + assertEquals(Boolean.FALSE, cloneServiceOfferingCmd.isLimitCpuUse()); + assertEquals(Boolean.TRUE, cloneServiceOfferingCmd.isVolatileVm()); + assertEquals("local", cloneServiceOfferingCmd.getStorageType()); + assertEquals("premium", cloneServiceOfferingCmd.getTags()); + assertEquals("gpu-enabled", cloneServiceOfferingCmd.getHostTag()); + assertEquals(Integer.valueOf(1000), cloneServiceOfferingCmd.getNetworkRate()); + assertEquals("UserDispersingPlanner", cloneServiceOfferingCmd.getDeploymentPlanner()); + assertTrue(cloneServiceOfferingCmd.isPurgeResources()); + } + + @Test + public void testSourceOfferingIdNullByDefault() { + assertNull(cloneServiceOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testGetSystemVmType() { + String systemVmType = "domainrouter"; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "systemVmType", systemVmType); + assertEquals(systemVmType, cloneServiceOfferingCmd.getSystemVmType()); + } + + @Test + public void testGetBytesReadRate() { + Long bytesReadRate = 1000000L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesReadRate", bytesReadRate); + assertEquals(bytesReadRate, cloneServiceOfferingCmd.getBytesReadRate()); + } + + @Test + public void testGetBytesWriteRate() { + Long bytesWriteRate = 1000000L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesWriteRate", bytesWriteRate); + assertEquals(bytesWriteRate, cloneServiceOfferingCmd.getBytesWriteRate()); + } + + @Test + public void testGetIopsReadRate() { + Long iopsReadRate = 1000L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsReadRate", iopsReadRate); + assertEquals(iopsReadRate, cloneServiceOfferingCmd.getIopsReadRate()); + } + + @Test + public void testGetIopsWriteRate() { + Long iopsWriteRate = 1000L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsWriteRate", iopsWriteRate); + assertEquals(iopsWriteRate, cloneServiceOfferingCmd.getIopsWriteRate()); + } + + @Test + public void testCloneServiceOfferingWithGpuProfile() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "GPU-Offering-Clone"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "vgpuProfileId", 10L); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuCount", 2); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuDisplay", true); + + assertEquals(Long.valueOf(10L), cloneServiceOfferingCmd.getVgpuProfileId()); + assertEquals(Integer.valueOf(2), cloneServiceOfferingCmd.getGpuCount()); + assertTrue(cloneServiceOfferingCmd.getGpuDisplay()); + } + + @Test + public void testCloneServiceOfferingWithLease() { + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", 555L); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", "Lease-Offering-Clone"); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseDuration", 7200); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", "destroy"); + + assertEquals(Integer.valueOf(7200), cloneServiceOfferingCmd.getLeaseDuration()); + assertEquals(VMLeaseManager.ExpiryAction.DESTROY, cloneServiceOfferingCmd.getLeaseExpiryAction()); + } + + @Test + public void testExecuteWithOverriddenParameters() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + String newName = "ClonedOffering-Override"; + String newDisplayText = "Overridden Display Text"; + Integer newCpu = 8; + Integer newMemory = 16384; + Integer newCpuSpeed = 3000; + Boolean newOfferHa = true; + Boolean newLimitCpuUse = true; + String newStorageType = "shared"; + String newTags = "premium,gpu"; + String newHostTag = "compute-optimized"; + Integer newNetworkRate = 2000; + String newDeploymentPlanner = "FirstFitPlanner"; + Boolean newPurgeResources = true; + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "displayText", newDisplayText); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", newCpu); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", newMemory); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuSpeed", newCpuSpeed); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "offerHa", newOfferHa); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "limitCpuUse", newLimitCpuUse); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "storageType", newStorageType); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "tags", newTags); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "hostTag", newHostTag); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "networkRate", newNetworkRate); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "deploymentPlanner", newDeploymentPlanner); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "purgeResources", newPurgeResources); + + assertEquals(sourceOfferingId, cloneServiceOfferingCmd.getSourceOfferingId()); + assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName()); + assertEquals(newDisplayText, cloneServiceOfferingCmd.getDisplayText()); + assertEquals(newCpu, cloneServiceOfferingCmd.getCpuNumber()); + assertEquals(newMemory, cloneServiceOfferingCmd.getMemory()); + assertEquals(newCpuSpeed, cloneServiceOfferingCmd.getCpuSpeed()); + assertEquals(newOfferHa, cloneServiceOfferingCmd.isOfferHa()); + assertEquals(newLimitCpuUse, cloneServiceOfferingCmd.isLimitCpuUse()); + assertEquals(newStorageType, cloneServiceOfferingCmd.getStorageType()); + assertEquals(newTags, cloneServiceOfferingCmd.getTags()); + assertEquals(newHostTag, cloneServiceOfferingCmd.getHostTag()); + assertEquals(newNetworkRate, cloneServiceOfferingCmd.getNetworkRate()); + assertEquals(newDeploymentPlanner, cloneServiceOfferingCmd.getDeploymentPlanner()); + assertTrue(cloneServiceOfferingCmd.isPurgeResources()); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteWithPartialOverrides() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + String newName = "PartialOverride"; + Integer newCpu = 6; + Integer newMemory = 12288; + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "cpuNumber", newCpu); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "memory", newMemory); + + assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName()); + assertEquals(newCpu, cloneServiceOfferingCmd.getCpuNumber()); + assertEquals(newMemory, cloneServiceOfferingCmd.getMemory()); + + assertNull(cloneServiceOfferingCmd.getCpuSpeed()); + assertFalse(cloneServiceOfferingCmd.isOfferHa()); + assertNull(cloneServiceOfferingCmd.getStorageType()); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteWithGpuOverrides() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + String newName = "GPU-Clone-Override"; + Long vgpuProfileId = 15L; + Integer gpuCount = 4; + Boolean gpuDisplay = false; + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "vgpuProfileId", vgpuProfileId); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuCount", gpuCount); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "gpuDisplay", gpuDisplay); + + assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName()); + assertEquals(vgpuProfileId, cloneServiceOfferingCmd.getVgpuProfileId()); + assertEquals(gpuCount, cloneServiceOfferingCmd.getGpuCount()); + assertEquals(gpuDisplay, cloneServiceOfferingCmd.getGpuDisplay()); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteWithLeaseOverrides() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + String newName = "Lease-Clone-Override"; + Integer leaseDuration = 14400; // 4 hours + String leaseExpiryAction = "stop"; + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseDuration", leaseDuration); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "leaseExpiryAction", leaseExpiryAction); + + assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName()); + assertEquals(leaseDuration, cloneServiceOfferingCmd.getLeaseDuration()); + assertEquals(VMLeaseManager.ExpiryAction.STOP, cloneServiceOfferingCmd.getLeaseExpiryAction()); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteWithStorageOverrides() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + String newName = "Storage-Clone-Override"; + Long bytesReadRate = 2000000L; + Long bytesWriteRate = 1500000L; + Long iopsReadRate = 2000L; + Long iopsWriteRate = 1500L; + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesReadRate", bytesReadRate); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "bytesWriteRate", bytesWriteRate); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsReadRate", iopsReadRate); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "iopsWriteRate", iopsWriteRate); + + assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName()); + assertEquals(bytesReadRate, cloneServiceOfferingCmd.getBytesReadRate()); + assertEquals(bytesWriteRate, cloneServiceOfferingCmd.getBytesWriteRate()); + assertEquals(iopsReadRate, cloneServiceOfferingCmd.getIopsReadRate()); + assertEquals(iopsWriteRate, cloneServiceOfferingCmd.getIopsWriteRate()); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } + + @Test + public void testExecuteWithDetailsOverride() { + Long sourceOfferingId = 555L; + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "sourceOfferingId", sourceOfferingId); + + String newName = "Details-Clone-Override"; + Map> details = new HashMap<>(); + + HashMap cpuOvercommit = new HashMap<>(); + cpuOvercommit.put("key", "cpuOvercommitRatio"); + cpuOvercommit.put("value", "3.0"); + + HashMap memoryOvercommit = new HashMap<>(); + memoryOvercommit.put("key", "memoryOvercommitRatio"); + memoryOvercommit.put("value", "2.5"); + + HashMap customDetail = new HashMap<>(); + customDetail.put("key", "customParameter"); + customDetail.put("value", "customValue"); + + details.put("0", cpuOvercommit); + details.put("1", memoryOvercommit); + details.put("2", customDetail); + + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "serviceOfferingName", newName); + ReflectionTestUtils.setField(cloneServiceOfferingCmd, "details", details); + + assertEquals(newName, cloneServiceOfferingCmd.getServiceOfferingName()); + Map result = cloneServiceOfferingCmd.getDetails(); + assertNotNull(result); + assertEquals("3.0", result.get("cpuOvercommitRatio")); + assertEquals("2.5", result.get("memoryOvercommitRatio")); + assertEquals("customValue", result.get("customParameter")); + + when(configService.cloneServiceOffering(any(CloneServiceOfferingCmd.class))).thenReturn(mockServiceOffering); + when(responseGenerator.createServiceOfferingResponse(mockServiceOffering)).thenReturn(mockServiceOfferingResponse); + + cloneServiceOfferingCmd.execute(); + + assertNotNull(cloneServiceOfferingCmd.getResponseObject()); + assertEquals(mockServiceOfferingResponse, cloneServiceOfferingCmd.getResponseObject()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java index 6daa5de07cbf..bc7f65b07561 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/offering/CreateServiceOfferingCmdTest.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.api.command.admin.offering; +import com.cloud.exception.InvalidParameterValueException; +import org.apache.cloudstack.vm.lease.VMLeaseManager; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -55,4 +57,44 @@ public void testIsPurgeResourcesTrue() { ReflectionTestUtils.setField(createServiceOfferingCmd, "purgeResources", true); Assert.assertTrue(createServiceOfferingCmd.isPurgeResources()); } + + @Test + public void testGetLeaseDuration() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseDuration", 10); + Assert.assertEquals(10, createServiceOfferingCmd.getLeaseDuration().longValue()); + } + + @Test + public void testGetLeaseExpiryAction() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseExpiryAction", "stop"); + Assert.assertEquals(VMLeaseManager.ExpiryAction.STOP, createServiceOfferingCmd.getLeaseExpiryAction()); + + ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseExpiryAction", "DESTROY"); + Assert.assertEquals(VMLeaseManager.ExpiryAction.DESTROY, createServiceOfferingCmd.getLeaseExpiryAction()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetLeaseExpiryActionInvalidValue() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "leaseExpiryAction", "Unknown"); + Assert.assertEquals(null, createServiceOfferingCmd.getLeaseExpiryAction()); + } + + @Test + public void testGetVgpuProfileId() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "vgpuProfileId", 10L); + Assert.assertEquals(10L, createServiceOfferingCmd.getVgpuProfileId().longValue()); + } + + @Test + public void testGetGpuCount() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "gpuCount", 2); + Assert.assertEquals(2, createServiceOfferingCmd.getGpuCount().intValue()); + } + + @Test + public void testGetGpuDisplay() { + ReflectionTestUtils.setField(createServiceOfferingCmd, "gpuDisplay", true); + Assert.assertTrue(createServiceOfferingCmd.getGpuDisplay()); + } + } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java index a628f13275c5..c8ca1c2afe30 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/resource/PurgeExpungedResourcesCmdTest.java @@ -44,7 +44,7 @@ public class PurgeExpungedResourcesCmdTest { @Spy @InjectMocks - PurgeExpungedResourcesCmd spyCmd = Mockito.spy(new PurgeExpungedResourcesCmd()); + PurgeExpungedResourcesCmd spyCmd; @Test public void testGetResourceType() { diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmdTest.java index a69a7a858ce0..c7aeb8ba99bf 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/AddObjectStoragePoolCmdTest.java @@ -61,6 +61,8 @@ public class AddObjectStoragePoolCmdTest { String provider = "Simulator"; + Long size = 10L; + Map details; private AutoCloseable closeable; @@ -74,6 +76,7 @@ public void setUp() throws Exception { ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "url", url); ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "providerName", provider); ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "details", details); + ReflectionTestUtils.setField(addObjectStoragePoolCmdSpy, "size", size); addObjectStoragePoolCmdSpy._storageService = storageService; addObjectStoragePoolCmdSpy._responseGenerator = responseGenerator; } @@ -87,12 +90,12 @@ public void tearDown() throws Exception { @Test public void testAddObjectStore() throws DiscoveryException { Mockito.doReturn(objectStore).when(storageService).discoverObjectStore(Mockito.anyString(), - Mockito.anyString(), Mockito.anyString(), any()); + Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), any()); ObjectStoreResponse objectStoreResponse = new ObjectStoreResponse(); Mockito.doReturn(objectStoreResponse).when(responseGenerator).createObjectStoreResponse(any()); addObjectStoragePoolCmdSpy.execute(); Mockito.verify(storageService, Mockito.times(1)) - .discoverObjectStore(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); + .discoverObjectStore(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any()); } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmdTest.java index 98435bf6e38a..8d2771c969b7 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/storage/DownloadImageStoreObjectCmdTest.java @@ -19,20 +19,19 @@ import com.cloud.utils.Pair; import org.apache.cloudstack.api.response.ExtractResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.storage.browser.StorageBrowser; -import org.junit.After; 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.MockitoAnnotations; import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; import java.util.List; +import java.util.UUID; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -48,18 +47,6 @@ public class DownloadImageStoreObjectCmdTest { @Spy private DownloadImageStoreObjectCmd cmd; - private AutoCloseable closeable; - - @Before - public void setUp() { - closeable = MockitoAnnotations.openMocks(this); - } - - @After - public void tearDown() throws Exception { - closeable.close(); - } - @Test public void testExecute() throws Exception { ReflectionTestUtils.setField(cmd, "storeId", 1L); @@ -110,10 +97,13 @@ public void testGetEventType() { @Test public void testGetEventDescription() { + String uuid = UUID.randomUUID().toString(); + ReflectionTestUtils.setField(cmd, "storeId", 1L); ReflectionTestUtils.setField(cmd, "path", "path/to/object"); + CallContext.current().putApiResourceUuid("id", uuid); String eventDescription = cmd.getEventDescription(); - Assert.assertEquals("Downloading object at path path/to/object on image store 1", eventDescription); + Assert.assertEquals(String.format("Downloading object at path path/to/object on image store %s", uuid), eventDescription); } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmdTest.java index 8a57ac3eb22c..397723dd6069 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/user/CreateUserCmdTest.java @@ -69,7 +69,7 @@ public void testExecuteWithNotBlankPassword() { } catch (ServerApiException e) { Assert.assertTrue("Received exception as the mock accountService createUser returns null user", true); } - Mockito.verify(accountService, Mockito.times(1)).createUser(null, "Test", null, null, null, null, null, null, null); + Mockito.verify(accountService, Mockito.times(1)).createUser(null, "Test", null, null, null, null, null, null, null, false); } @Test @@ -82,7 +82,7 @@ public void testExecuteWithNullPassword() { Assert.assertEquals(ApiErrorCode.PARAM_ERROR,e.getErrorCode()); Assert.assertEquals("Empty passwords are not allowed", e.getMessage()); } - Mockito.verify(accountService, Mockito.never()).createUser(null, null, null, null, null, null, null, null, null); + Mockito.verify(accountService, Mockito.never()).createUser(null, null, null, null, null, null, null, null, null, false); } @Test @@ -95,6 +95,6 @@ public void testExecuteWithEmptyPassword() { Assert.assertEquals(ApiErrorCode.PARAM_ERROR,e.getErrorCode()); Assert.assertEquals("Empty passwords are not allowed", e.getMessage()); } - Mockito.verify(accountService, Mockito.never()).createUser(null, null, null, null, null, null, null, null, null); + Mockito.verify(accountService, Mockito.never()).createUser(null, null, null, null, null, null, null, null, null, true); } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmdTest.java new file mode 100644 index 000000000000..f86e51adb5ab --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/user/UpdateUserCmdTest.java @@ -0,0 +1,64 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.admin.user; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(MockitoJUnitRunner.class) +public class UpdateUserCmdTest { + @InjectMocks + private UpdateUserCmd cmd; + + @Test + public void testGetApiResourceId() { + Long userId = 99L; + cmd.setId(userId); + Assert.assertEquals(userId, cmd.getApiResourceId()); + } + + @Test + public void testGetApiResourceType() { + Assert.assertEquals(ApiCommandResourceType.User, cmd.getApiResourceType()); + } + + @Test + public void testIsPasswordChangeRequired_True() { + ReflectionTestUtils.setField(cmd, "passwordChangeRequired", Boolean.TRUE); + Assert.assertTrue(cmd.isPasswordChangeRequired()); + } + + @Test + public void testIsPasswordChangeRequired_False() { + ReflectionTestUtils.setField(cmd, "passwordChangeRequired", Boolean.FALSE); + Assert.assertFalse(cmd.isPasswordChangeRequired()); + } + + @Test + public void testIsPasswordChangeRequired_Null() { + ReflectionTestUtils.setField(cmd, "passwordChangeRequired", null); + Assert.assertFalse(cmd.isPasswordChangeRequired()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmdTest.java new file mode 100644 index 000000000000..27bc4614e1b5 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/AssignVMCmdTest.java @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.vm; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +public class AssignVMCmdTest { + + @Test + public void test_setSkipNetwork_default() { + AssignVMCmd assignVMCmd = new AssignVMCmd(); + Object value = ReflectionTestUtils.getField(assignVMCmd, "skipNetwork"); + Assert.assertTrue(value instanceof Boolean); + Assert.assertFalse((Boolean) value); + } + + @Test + public void test_setSkipNetwork_set() { + AssignVMCmd assignVMCmd = new AssignVMCmd(); + assignVMCmd.setSkipNetwork(true); + Object value = ReflectionTestUtils.getField(assignVMCmd, "skipNetwork"); + Assert.assertTrue(value instanceof Boolean); + Assert.assertTrue((Boolean) value); + } + + @Test + public void test_isSkipNetwork_default() { + AssignVMCmd assignVMCmd = new AssignVMCmd(); + Assert.assertFalse(assignVMCmd.isSkipNetwork()); + } + + @Test + public void test_isSkipNetwork_set() { + AssignVMCmd assignVMCmd = new AssignVMCmd(); + ReflectionTestUtils.setField(assignVMCmd, "skipNetwork", true); + Assert.assertTrue(assignVMCmd.isSkipNetwork()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdminTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdminTest.java new file mode 100644 index 000000000000..d82dc9f766f9 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/DeployVMCmdByAdminTest.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.vm; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +@RunWith(MockitoJUnitRunner.class) +public class DeployVMCmdByAdminTest { + + @InjectMocks + private DeployVMCmdByAdmin cmd; + + @Test + public void testIsBlankInstance_default() { + assertFalse(cmd.isBlankInstance()); + } + + @Test + public void testIsBlankInstance_true() { + ReflectionTestUtils.setField(cmd, "blankInstance", true); + assertTrue(cmd.isBlankInstance()); + } + + @Test + public void testIsBlankInstance_false() { + ReflectionTestUtils.setField(cmd, "blankInstance", false); + assertFalse(cmd.isBlankInstance()); + } + + @Test + public void testSetBlankInstance_default() { + Object obj = ReflectionTestUtils.getField(cmd, "blankInstance"); + assertNull(obj); + } + + @Test + public void testSetBlankInstance_true() { + cmd.setBlankInstance(true); + Object obj = ReflectionTestUtils.getField(cmd, "blankInstance"); + assertNotNull(obj); + assertTrue((boolean)obj); + } + + @Test + public void testSetBlankInstance_false() { + cmd.setBlankInstance(false); + Object obj = ReflectionTestUtils.getField(cmd, "blankInstance"); + assertNotNull(obj); + assertFalse((boolean)obj); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmdTest.java index ca8bfa4a45c4..c0e3b1aba321 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vm/MigrateVirtualMachineWithVolumeCmdTest.java @@ -68,6 +68,12 @@ public class MigrateVirtualMachineWithVolumeCmdTest { @Mock Host hostMock; + @Mock + private Object job; + + @Mock + private Object _responseObject; + @Spy @InjectMocks MigrateVirtualMachineWithVolumeCmd cmdSpy; diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmdTest.java index a7c41b9271b1..235acb15eeab 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/ImportVolumeCmdTest.java @@ -46,7 +46,7 @@ public void testImportVolumeCmd() { Long projectId = 5L; long accountId = 6L; - Mockito.when(accountService.finalyzeAccountId(accountName, domainId, projectId, true)).thenReturn(accountId); + Mockito.when(accountService.finalizeAccountId(accountName, domainId, projectId, true)).thenReturn(accountId); ImportVolumeCmd cmd = new ImportVolumeCmd(); ReflectionTestUtils.setField(cmd, "path", path); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmdTest.java index ba7e351a8a8e..59a61806e867 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/volume/UnmanageVolumeCmdTest.java @@ -22,6 +22,7 @@ import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.storage.volume.VolumeImportUnmanageService; import org.junit.Assert; import org.junit.Test; @@ -31,6 +32,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + @RunWith(MockitoJUnitRunner.class) public class UnmanageVolumeCmdTest { @@ -41,6 +44,7 @@ public class UnmanageVolumeCmdTest { public void testUnmanageVolumeCmd() { long accountId = 2L; Long volumeId = 3L; + String volumeUuid = UUID.randomUUID().toString(); Volume volume = Mockito.mock(Volume.class); Mockito.when(responseGenerator.findVolumeById(volumeId)).thenReturn(volume); @@ -51,12 +55,14 @@ public void testUnmanageVolumeCmd() { ReflectionTestUtils.setField(cmd,"volumeImportService", volumeImportService); ReflectionTestUtils.setField(cmd,"_responseGenerator", responseGenerator); + CallContext.current().putApiResourceUuid("id", volumeUuid); + Assert.assertEquals(volumeId, cmd.getVolumeId()); Assert.assertEquals(accountId, cmd.getEntityOwnerId()); Assert.assertEquals(volumeId, cmd.getApiResourceId()); Assert.assertEquals(ApiCommandResourceType.Volume, cmd.getApiResourceType()); Assert.assertEquals(EventTypes.EVENT_VOLUME_UNMANAGE, cmd.getEventType()); - Assert.assertEquals("Unmanaging Volume with ID " + volumeId, cmd.getEventDescription()); + Assert.assertEquals("Unmanaging Volume with ID: " + volumeUuid, cmd.getEventDescription()); Mockito.when(volumeImportService.unmanageVolume(volumeId)).thenReturn(true); try { diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CloneVpcOfferingCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CloneVpcOfferingCmdTest.java new file mode 100644 index 000000000000..1e6d6c9e0969 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CloneVpcOfferingCmdTest.java @@ -0,0 +1,299 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.command.admin.vpc; + +import com.cloud.network.vpc.VpcOffering; +import com.cloud.network.vpc.VpcProvisioningService; +import org.apache.cloudstack.api.ApiErrorCode; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.VpcOfferingResponse; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +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.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class CloneVpcOfferingCmdTest { + + private CloneVPCOfferingCmd cloneVpcOfferingCmd; + + @Mock + private VpcProvisioningService vpcService; + + @Mock + private ResponseGenerator responseGenerator; + + @Mock + private VpcOffering mockVpcOffering; + + @Mock + private VpcOfferingResponse mockVpcOfferingResponse; + + @Before + public void setUp() { + cloneVpcOfferingCmd = new CloneVPCOfferingCmd(); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "_vpcProvSvc", vpcService); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "_responseGenerator", responseGenerator); + } + + @Test + public void testGetSourceOfferingId() { + Long sourceOfferingId = 789L; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", sourceOfferingId); + assertEquals(sourceOfferingId, cloneVpcOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testGetName() { + String name = "ClonedVpcOffering"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "vpcOfferingName", name); + assertEquals(name, cloneVpcOfferingCmd.getVpcOfferingName()); + } + + @Test + public void testGetDisplayText() { + String displayText = "Cloned VPC Offering Display Text"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "displayText", displayText); + assertEquals(displayText, cloneVpcOfferingCmd.getDisplayText()); + } + + @Test + public void testGetDisplayTextDefaultsToName() { + String name = "ClonedVpcOffering"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "vpcOfferingName", name); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "displayText", null); + assertEquals(name, cloneVpcOfferingCmd.getDisplayText()); + } + + @Test + public void testGetServiceOfferingId() { + Long serviceOfferingId = 456L; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceOfferingId", serviceOfferingId); + assertEquals(serviceOfferingId, cloneVpcOfferingCmd.getServiceOfferingId()); + } + + @Test + public void testGetInternetProtocol() { + String internetProtocol = "dualstack"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "internetProtocol", internetProtocol); + assertEquals(internetProtocol, cloneVpcOfferingCmd.getInternetProtocol()); + } + + @Test + public void testGetProvider() { + String provider = "NSX"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", provider); + assertEquals(provider, cloneVpcOfferingCmd.getProvider()); + } + + @Test + public void testGetNetworkMode() { + String networkMode = "ROUTED"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", networkMode); + assertEquals(networkMode, cloneVpcOfferingCmd.getNetworkMode()); + } + + @Test + public void testGetRoutingMode() { + String routingMode = "dynamic"; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "routingMode", routingMode); + assertEquals(routingMode, cloneVpcOfferingCmd.getRoutingMode()); + } + + @Test + public void testGetNsxSupportLb() { + Boolean nsxSupportLb = true; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "nsxSupportsLbService", nsxSupportLb); + assertEquals(nsxSupportLb, cloneVpcOfferingCmd.getNsxSupportsLbService()); + } + + @Test + public void testGetSpecifyAsnumber() { + Boolean specifyAsnumber = false; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "specifyAsNumber", specifyAsnumber); + assertEquals(specifyAsnumber, cloneVpcOfferingCmd.getSpecifyAsNumber()); + } + + @Test + public void testExecuteSuccess() { + Long sourceOfferingId = 789L; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(vpcService.cloneVPCOffering(any(CloneVPCOfferingCmd.class))).thenReturn(mockVpcOffering); + when(responseGenerator.createVpcOfferingResponse(mockVpcOffering)).thenReturn(mockVpcOfferingResponse); + + cloneVpcOfferingCmd.execute(); + + assertNotNull(cloneVpcOfferingCmd.getResponseObject()); + assertEquals(mockVpcOfferingResponse, cloneVpcOfferingCmd.getResponseObject()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteFailure() { + Long sourceOfferingId = 789L; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", sourceOfferingId); + + when(vpcService.cloneVPCOffering(any(CloneVPCOfferingCmd.class))).thenReturn(null); + + try { + cloneVpcOfferingCmd.execute(); + fail("Expected ServerApiException to be thrown"); + } catch (ServerApiException e) { + assertEquals(ApiErrorCode.INTERNAL_ERROR, e.getErrorCode()); + assertEquals("Failed to clone VPC offering", e.getMessage()); + throw e; + } + } + + @Test + public void testGetSupportedServices() { + List supportedServices = Arrays.asList("Dhcp", "Dns", "SourceNat", "NetworkACL"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "supportedServices", supportedServices); + assertEquals(supportedServices, cloneVpcOfferingCmd.getSupportedServices()); + } + + @Test + public void testGetServiceProviders() { + Map> serviceProviderList = new HashMap<>(); + + HashMap dhcpProvider = new HashMap<>(); + dhcpProvider.put("service", "Dhcp"); + dhcpProvider.put("provider", "VpcVirtualRouter"); + + HashMap dnsProvider = new HashMap<>(); + dnsProvider.put("service", "Dns"); + dnsProvider.put("provider", "VpcVirtualRouter"); + + HashMap aclProvider = new HashMap<>(); + aclProvider.put("service", "NetworkACL"); + aclProvider.put("provider", "VpcVirtualRouter"); + + serviceProviderList.put("0", dhcpProvider); + serviceProviderList.put("1", dnsProvider); + serviceProviderList.put("2", aclProvider); + + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceProviderList", serviceProviderList); + + Map> result = cloneVpcOfferingCmd.getServiceProviders(); + assertNotNull(result); + assertEquals(3, result.size()); + assertNotNull(result.get("Dhcp")); + assertNotNull(result.get("Dns")); + assertNotNull(result.get("NetworkACL")); + assertEquals("VpcVirtualRouter", result.get("Dhcp").get(0)); + assertEquals("VpcVirtualRouter", result.get("Dns").get(0)); + assertEquals("VpcVirtualRouter", result.get("NetworkACL").get(0)); + } + + @Test + public void testGetEnable() { + Boolean enable = true; + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "enable", enable); + assertEquals(enable, cloneVpcOfferingCmd.getEnable()); + } + + @Test + public void testCloneWithAllParameters() { + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", 789L); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "vpcOfferingName", "ClonedVpcOffering"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "displayText", "Cloned VPC Offering"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceOfferingId", 456L); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "internetProtocol", "ipv4"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", "NSX"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", "NATTED"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "routingMode", "static"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "nsxSupportsLbService", true); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "specifyAsNumber", false); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "enable", true); + + assertEquals(Long.valueOf(789L), cloneVpcOfferingCmd.getSourceOfferingId()); + assertEquals("ClonedVpcOffering", cloneVpcOfferingCmd.getVpcOfferingName()); + assertEquals("Cloned VPC Offering", cloneVpcOfferingCmd.getDisplayText()); + assertEquals(Long.valueOf(456L), cloneVpcOfferingCmd.getServiceOfferingId()); + assertEquals("ipv4", cloneVpcOfferingCmd.getInternetProtocol()); + assertEquals("NSX", cloneVpcOfferingCmd.getProvider()); + assertEquals("NATTED", cloneVpcOfferingCmd.getNetworkMode()); + assertEquals("static", cloneVpcOfferingCmd.getRoutingMode()); + assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getNsxSupportsLbService()); + assertEquals(Boolean.FALSE, cloneVpcOfferingCmd.getSpecifyAsNumber()); + assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getEnable()); + } + + @Test + public void testSourceOfferingIdNullByDefault() { + assertNull(cloneVpcOfferingCmd.getSourceOfferingId()); + } + + @Test + public void testProviderNullByDefault() { + assertNull(cloneVpcOfferingCmd.getProvider()); + } + + @Test + public void testServiceCapabilityList() { + Map> serviceCapabilityList = new HashMap<>(); + serviceCapabilityList.put("Connectivity", Arrays.asList("RegionLevelVpc:true", "DistributedRouter:true")); + serviceCapabilityList.put("SourceNat", Arrays.asList("RedundantRouter:true")); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "serviceCapabilityList", serviceCapabilityList); + + Map> result = cloneVpcOfferingCmd.getServiceCapabilityList(); + assertNotNull(result); + assertEquals(serviceCapabilityList, result); + } + + @Test + public void testCloneVpcOfferingWithNsxProvider() { + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", 789L); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", "NSX"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "nsxSupportsLbService", true); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", "ROUTED"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "routingMode", "dynamic"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "specifyAsNumber", true); + + assertEquals("NSX", cloneVpcOfferingCmd.getProvider()); + assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getNsxSupportsLbService()); + assertEquals("ROUTED", cloneVpcOfferingCmd.getNetworkMode()); + assertEquals("dynamic", cloneVpcOfferingCmd.getRoutingMode()); + assertEquals(Boolean.TRUE, cloneVpcOfferingCmd.getSpecifyAsNumber()); + } + + @Test + public void testCloneVpcOfferingWithNetrisProvider() { + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "sourceOfferingId", 789L); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "provider", "Netris"); + ReflectionTestUtils.setField(cloneVpcOfferingCmd, "networkMode", "NATTED"); + + assertEquals("Netris", cloneVpcOfferingCmd.getProvider()); + assertEquals("NATTED", cloneVpcOfferingCmd.getNetworkMode()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCCmdByAdminTest.java b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCCmdByAdminTest.java index c4e21bb948b2..aaa65e10ff4e 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCCmdByAdminTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/admin/vpc/CreateVPCCmdByAdminTest.java @@ -20,7 +20,6 @@ import com.cloud.network.vpc.VpcService; import com.cloud.user.AccountService; import com.cloud.utils.db.EntityManager; -import junit.framework.TestCase; import org.apache.cloudstack.api.ResponseGenerator; import org.junit.Assert; import org.junit.Test; @@ -34,7 +33,7 @@ import java.util.List; @RunWith(MockitoJUnitRunner.class) -public class CreateVPCCmdByAdminTest extends TestCase { +public class CreateVPCCmdByAdminTest { @Mock public VpcService _vpcService; @@ -43,8 +42,10 @@ public class CreateVPCCmdByAdminTest extends TestCase { @Mock public AccountService _accountService; private ResponseGenerator responseGenerator; + @Mock + public Object job; @InjectMocks - CreateVPCCmdByAdmin cmd = new CreateVPCCmdByAdmin(); + CreateVPCCmdByAdmin cmd; @Test public void testBgpPeerIds() { diff --git a/api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java b/api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java new file mode 100644 index 000000000000..e679bbf2d1f1 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/offering/DomainAndZoneIdResolverTest.java @@ -0,0 +1,149 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.offering; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.LongFunction; + +import com.cloud.dc.DataCenter; +import com.cloud.domain.Domain; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.utils.db.EntityManager; +import org.apache.cloudstack.api.BaseCmd; +import org.apache.cloudstack.api.ServerApiException; +import org.junit.Assert; +import org.junit.Test; + +public class DomainAndZoneIdResolverTest { + static class TestCmd extends BaseCmd implements DomainAndZoneIdResolver { + @Override + public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException, NetworkRuleConflictException { + // No implementation needed for tests + } + + @Override + public String getCommandName() { + return "test"; + } + + @Override + public long getEntityOwnerId() { + return 1L; + } + } + + private void setEntityMgr(final BaseCmd cmd, final EntityManager entityMgr) throws Exception { + Field f = BaseCmd.class.getDeclaredField("_entityMgr"); + f.setAccessible(true); + f.set(cmd, entityMgr); + } + + @Test + public void resolveDomainIds_usesDefaultProviderWhenEmpty() { + TestCmd cmd = new TestCmd(); + + final LongFunction> defaultsProvider = id -> Arrays.asList(100L, 200L); + + List result = cmd.resolveDomainIds("", 42L, defaultsProvider, "offering"); + Assert.assertEquals(Arrays.asList(100L, 200L), result); + } + + @Test + public void resolveDomainIds_resolvesValidUuids() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + Domain d1 = mock(Domain.class); + when(d1.getId()).thenReturn(10L); + Domain d2 = mock(Domain.class); + when(d2.getId()).thenReturn(20L); + + when(em.findByUuid(Domain.class, "uuid1")).thenReturn(d1); + when(em.findByUuid(Domain.class, "uuid2")).thenReturn(d2); + + List ids = cmd.resolveDomainIds("uuid1, public, uuid2", null, null, "template"); + Assert.assertEquals(Arrays.asList(10L, 20L), ids); + } + + @Test + public void resolveDomainIds_invalidUuid_throws() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + when(em.findByUuid(Domain.class, "bad-uuid")).thenReturn(null); + + Assert.assertThrows(InvalidParameterValueException.class, + () -> cmd.resolveDomainIds("bad-uuid", null, null, "offering")); + } + + @Test + public void resolveZoneIds_usesDefaultProviderWhenEmpty() { + TestCmd cmd = new TestCmd(); + + final LongFunction> defaultsProvider = id -> Collections.singletonList(300L); + + List result = cmd.resolveZoneIds("", 99L, defaultsProvider, "offering"); + Assert.assertEquals(Collections.singletonList(300L), result); + } + + @Test + public void resolveZoneIds_resolvesValidUuids() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + DataCenter z1 = mock(DataCenter.class); + when(z1.getId()).thenReturn(30L); + DataCenter z2 = mock(DataCenter.class); + when(z2.getId()).thenReturn(40L); + + when(em.findByUuid(DataCenter.class, "zone-1")).thenReturn(z1); + when(em.findByUuid(DataCenter.class, "zone-2")).thenReturn(z2); + + List ids = cmd.resolveZoneIds("zone-1, all, zone-2", null, null, "service"); + Assert.assertEquals(Arrays.asList(30L, 40L), ids); + } + + @Test + public void resolveZoneIds_invalidUuid_throws() throws Exception { + TestCmd cmd = new TestCmd(); + + EntityManager em = mock(EntityManager.class); + setEntityMgr(cmd, em); + + when(em.findByUuid(DataCenter.class, "bad-zone")).thenReturn(null); + + Assert.assertThrows(InvalidParameterValueException.class, + () -> cmd.resolveZoneIds("bad-zone", null, null, "offering")); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java index 9ea3a6446e8e..71c6d54c7a8c 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/AddIpToVmNicTest.java @@ -59,7 +59,7 @@ public void testCreateSuccess() throws ResourceAllocationException, ResourceUnav NicSecondaryIp secIp = Mockito.mock(NicSecondaryIp.class); Mockito.when( - networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any())) + networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any(), ArgumentMatchers.anyString())) .thenReturn(secIp); ipTonicCmd._networkService = networkService; @@ -79,7 +79,7 @@ public void testCreateFailure() throws ResourceAllocationException, ResourceUnav AddIpToVmNicCmd ipTonicCmd = Mockito.mock(AddIpToVmNicCmd.class); Mockito.when( - networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any())) + networkService.allocateSecondaryGuestIP(ArgumentMatchers.anyLong(), ArgumentMatchers.any(), ArgumentMatchers.anyString())) .thenReturn(null); ipTonicCmd._networkService = networkService; diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java index a40cb6dd26b9..032dca8d8007 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/CreateSnapshotCmdTest.java @@ -25,11 +25,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.command.user.snapshot.CreateSnapshotCmd; import org.apache.cloudstack.api.response.SnapshotResponse; +import org.apache.cloudstack.context.CallContext; import org.junit.Assert; import org.junit.Before; import org.junit.Rule; @@ -73,11 +75,6 @@ public Long getVolumeId(){ public long getEntityOwnerId(){ return 1L; } - - @Override - protected String getVolumeUuid() { - return "123"; - } }; } @@ -93,7 +90,7 @@ public void testCreateSuccess() { Snapshot snapshot = Mockito.mock(Snapshot.class); try { Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), isNull(), - nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class))).thenReturn(snapshot); + nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), nullable(Map.class), nullable(List.class), nullable(List.class), Mockito.anyBoolean())).thenReturn(snapshot); } catch (Exception e) { Assert.fail("Received exception when success expected " + e.getMessage()); @@ -121,12 +118,15 @@ public void testCreateFailure() { AccountService accountService = Mockito.mock(AccountService.class); Account account = Mockito.mock(Account.class); Mockito.when(accountService.getAccount(anyLong())).thenReturn(account); + String volumeUuid = UUID.randomUUID().toString(); + + CallContext.current().putApiResourceUuid("volumeid", volumeUuid); VolumeApiService volumeApiService = Mockito.mock(VolumeApiService.class); try { Mockito.when(volumeApiService.takeSnapshot(nullable(Long.class), nullable(Long.class), nullable(Long.class), - nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), any(), Mockito.anyList())).thenReturn(null); + nullable(Account.class), nullable(Boolean.class), nullable(Snapshot.LocationType.class), nullable(Boolean.class), any(), Mockito.anyList(), Mockito.anyList(), Mockito.anyBoolean())).thenReturn(null); } catch (Exception e) { Assert.fail("Received exception when success expected " + e.getMessage()); } @@ -137,7 +137,7 @@ public void testCreateFailure() { try { createSnapshotCmd.execute(); } catch (ServerApiException exception) { - Assert.assertEquals("Failed to create Snapshot due to an internal error creating Snapshot for volume 123", exception.getDescription()); + Assert.assertEquals("Failed to create Snapshot due to an internal error creating Snapshot for volume " + volumeUuid, exception.getDescription()); } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java index 46ff0e14c8f3..1150c40ba488 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/ScaleVMCmdTest.java @@ -78,10 +78,6 @@ public void testCreateSuccess() { scaleVMCmd._responseGenerator = responseGenerator; UserVmResponse userVmResponse = Mockito.mock(UserVmResponse.class); - //List list = Mockito.mock(UserVmResponse.class); - //list.add(userVmResponse); - //LinkedList mockedList = Mockito.mock(LinkedList.class); - //Mockito.when(mockedList.get(0)).thenReturn(userVmResponse); List list = new LinkedList(); list.add(userVmResponse); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateAutoScaleVmProfileCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateAutoScaleVmProfileCmdTest.java index da6ed31eaa2a..3409ce053a9f 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateAutoScaleVmProfileCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateAutoScaleVmProfileCmdTest.java @@ -100,7 +100,7 @@ public void verifyUpdateAutoScaleVmProfileCmd() { Assert.assertEquals("updateautoscalevmprofileresponse", updateAutoScaleVmProfileCmd.getCommandName()); Assert.assertEquals(EventTypes.EVENT_AUTOSCALEVMPROFILE_UPDATE, updateAutoScaleVmProfileCmd.getEventType()); - Assert.assertEquals("Updating AutoScale Instance Profile. Instance Profile Id: " + profileId, updateAutoScaleVmProfileCmd.getEventDescription()); + Assert.assertEquals("Updating AutoScale Instance Profile with ID: " + profileId, updateAutoScaleVmProfileCmd.getEventDescription()); } @Test diff --git a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateConditionCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateConditionCmdTest.java index 7d6f8dc35b7e..c78dbe9b56bc 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateConditionCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/test/UpdateConditionCmdTest.java @@ -31,12 +31,15 @@ import org.apache.cloudstack.api.command.user.autoscale.UpdateConditionCmd; import org.apache.cloudstack.api.response.ConditionResponse; +import org.apache.cloudstack.context.CallContext; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + import static org.mockito.Mockito.when; public class UpdateConditionCmdTest { @@ -53,6 +56,7 @@ public class UpdateConditionCmdTest { private static final Long threshold = 100L; private static final long accountId = 5L; + private static final String conditionUuid = UUID.randomUUID().toString(); @Before public void setUp() { @@ -71,6 +75,8 @@ public void setUp() { ReflectionTestUtils.setField(updateConditionCmd,"relationalOperator", relationalOperator); ReflectionTestUtils.setField(updateConditionCmd,"threshold", threshold); + CallContext.current().putApiResourceUuid("id", conditionUuid); + condition = Mockito.mock(Condition.class); } @@ -83,7 +89,7 @@ public void verifyUpdateConditionCmd() { Assert.assertEquals(ApiCommandResourceType.Condition, updateConditionCmd.getApiResourceType()); Assert.assertEquals("updateconditionresponse", updateConditionCmd.getCommandName()); Assert.assertEquals(EventTypes.EVENT_CONDITION_UPDATE, updateConditionCmd.getEventType()); - Assert.assertEquals("Updating a condition.", updateConditionCmd.getEventDescription()); + Assert.assertEquals("Updating Instance AutoScale condition with ID: " + conditionUuid, updateConditionCmd.getEventDescription()); when(entityMgr.findById(Condition.class, conditionId)).thenReturn(condition); when(condition.getAccountId()).thenReturn(accountId); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmdTest.java new file mode 100644 index 000000000000..a0d88bbc84e9 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/backup/ListBackupScheduleCmdTest.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.backup; + +import com.cloud.exception.InsufficientCapacityException; +import com.cloud.exception.NetworkRuleConflictException; +import com.cloud.exception.ResourceAllocationException; +import com.cloud.exception.ResourceUnavailableException; +import com.cloud.user.Account; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.response.BackupScheduleResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.BackupSchedule; +import org.apache.cloudstack.context.CallContext; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.ArrayList; +import java.util.List; + +@RunWith(MockitoJUnitRunner.class) +public class ListBackupScheduleCmdTest { + + @Mock + private BackupManager backupManager; + + @Mock + private ResponseGenerator responseGenerator; + + private ListBackupScheduleCmd cmd; + + @Before + public void setUp() { + cmd = new ListBackupScheduleCmd(); + cmd.backupManager = backupManager; + cmd._responseGenerator = responseGenerator; + } + + @Test + public void testExecuteWithSchedules() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException, NetworkRuleConflictException { + BackupSchedule schedule = Mockito.mock(BackupSchedule.class); + BackupScheduleResponse scheduleResponse = Mockito.mock(BackupScheduleResponse.class); + List schedules = new ArrayList<>(); + schedules.add(schedule); + + Mockito.when(backupManager.listBackupSchedules(cmd)).thenReturn(schedules); + Mockito.when(responseGenerator.createBackupScheduleResponse(schedule)).thenReturn(scheduleResponse); + + Account mockAccount = Mockito.mock(Account.class); + CallContext callContext = Mockito.mock(CallContext.class); + try (org.mockito.MockedStatic mocked = Mockito.mockStatic(CallContext.class)) { + cmd.execute(); + } + + ListResponse response = (ListResponse) cmd.getResponseObject(); + Assert.assertNotNull(response); + Assert.assertEquals(1, response.getResponses().size()); + Assert.assertEquals(scheduleResponse, response.getResponses().get(0)); + } + + @Test + public void testExecuteWithNoSchedules() { + Mockito.when(backupManager.listBackupSchedules(cmd)).thenReturn(new ArrayList<>()); + CallContext callContext = Mockito.mock(CallContext.class); + + try (org.mockito.MockedStatic mocked = Mockito.mockStatic(CallContext.class)) { + mocked.when(CallContext::current).thenReturn(callContext); + cmd.execute(); + } catch (ResourceUnavailableException | InsufficientCapacityException | ResourceAllocationException | + NetworkRuleConflictException e) { + throw new RuntimeException(e); + } + + ListResponse response = (ListResponse) cmd.getResponseObject(); + Assert.assertNotNull(response); + Assert.assertEquals(0, response.getResponses().size()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java new file mode 100644 index 000000000000..47bef14bb615 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/consoleproxy/ListConsoleSessionsCmdTest.java @@ -0,0 +1,124 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License.import org.apache.cloudstack.context.CallContext; +package org.apache.cloudstack.api.command.user.consoleproxy; + +import org.apache.cloudstack.consoleproxy.ConsoleSession; +import com.cloud.user.AccountService; + +import com.cloud.user.UserAccount; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.consoleproxy.ConsoleAccessManager; + +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; + +@RunWith(MockitoJUnitRunner.class) +public class ListConsoleSessionsCmdTest { + @Mock + private AccountService accountServiceMock; + + @Mock + private ConsoleAccessManager consoleAccessManagerMock; + + @Spy + @InjectMocks + private ListConsoleSessionsCmd listConsoleSessionsCmdSpy; + + @Test + public void executeTestApiExecutionShouldCallServiceLayer() { + Mockito.when(consoleAccessManagerMock.listConsoleSessions(listConsoleSessionsCmdSpy)).thenReturn(new ListResponse<>()); + listConsoleSessionsCmdSpy.execute(); + Mockito.verify(consoleAccessManagerMock).listConsoleSessions(listConsoleSessionsCmdSpy); + } + + @Test + public void getEntityOwnerIdTestReturnConsoleSessionIdIfProvided() { + ConsoleSession consoleSessionMock = Mockito.mock(ConsoleSession.class); + long consoleSessionId = 2L; + long accountId = 2L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(consoleSessionId); + Mockito.when(consoleAccessManagerMock.listConsoleSessionById(consoleSessionId)).thenReturn(consoleSessionMock); + Mockito.when(consoleSessionMock.getAccountId()).thenReturn(accountId); + + Assert.assertEquals(accountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnAccountIdWhenNoConsoleSessionIdIsProvided() { + long accountId = 2L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(accountId); + + Assert.assertEquals(accountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnUserIdWhenNoConsoleSessionIdAndAccountIdAreProvided() { + UserAccount userAccountMock = Mockito.mock(UserAccount.class); + long userId = 2L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(userId); + Mockito.when(accountServiceMock.getUserAccountById(userId)).thenReturn(userAccountMock); + Mockito.when(userAccountMock.getAccountId()).thenReturn(userId); + + Assert.assertEquals(userId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnSystemAccountIdWhenNoConsoleSessionIdAndAccountIdAndUserIdAreProvided() { + long systemAccountId = 1L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getAccountId()).thenReturn(null); + Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(null); + + Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnSystemAccountIdWhenConsoleSessionDoesNotExist() { + long consoleSessionId = 2L; + long systemAccountId = 1L; + + Mockito.when(listConsoleSessionsCmdSpy.getId()).thenReturn(consoleSessionId); + Mockito.when(consoleAccessManagerMock.listConsoleSessionById(consoleSessionId)).thenReturn(null); + + Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } + + @Test + public void getEntityOwnerIdTestReturnSystemAccountIdWhenUserAccountDoesNotExist() { + long userId = 2L; + long systemAccountId = 1L; + + Mockito.when(listConsoleSessionsCmdSpy.getUserId()).thenReturn(userId); + Mockito.when(accountServiceMock.getUserAccountById(userId)).thenReturn(null); + + Assert.assertEquals(systemAccountId, listConsoleSessionsCmdSpy.getEntityOwnerId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmdTest.java new file mode 100644 index 000000000000..2824a8dacb94 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/AddDnsServerCmdTest.java @@ -0,0 +1,134 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.dns.DnsProviderType; +import org.apache.cloudstack.dns.DnsServer; +import org.junit.Test; + +public class AddDnsServerCmdTest extends BaseDnsCmdTest { + + private AddDnsServerCmd createCmd() throws Exception { + AddDnsServerCmd cmd = new AddDnsServerCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "name", "test-dns"); + setField(cmd, "url", "http://dns.example.com"); + setField(cmd, "provider", "PowerDNS"); + setField(cmd, "dnsApiKey", "api-key-123"); + setField(cmd, "port", 8081); + setField(cmd, "isPublic", true); + setField(cmd, "publicDomainSuffix", "public.example.com"); + setField(cmd, "nameServers", Arrays.asList("ns1.example.com", "ns2.example.com")); + setField(cmd, "dnsUserName", "admin@example.com"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + AddDnsServerCmd cmd = createCmd(); + + assertEquals("test-dns", cmd.getName()); + assertEquals("http://dns.example.com", cmd.getUrl()); + assertEquals("api-key-123", cmd.getDnsApiKey()); + assertEquals(Integer.valueOf(8081), cmd.getPort()); + assertTrue(cmd.isPublic()); + assertEquals("public.example.com", cmd.getPublicDomainSuffix()); + assertEquals(Arrays.asList("ns1.example.com", "ns2.example.com"), cmd.getNameServers()); + assertEquals(DnsProviderType.PowerDNS, cmd.getProvider()); + assertEquals("admin@example.com", cmd.getDnsUserName()); + } + + @Test + public void testIsPublicFalse() throws Exception { + AddDnsServerCmd cmd = createCmd(); + setField(cmd, "isPublic", false); + assertFalse(cmd.isPublic()); + } + + @Test + public void testIsPublicNull() throws Exception { + AddDnsServerCmd cmd = createCmd(); + setField(cmd, "isPublic", null); + assertFalse(cmd.isPublic()); + } + + @Test + public void testGetEntityOwnerId() throws Exception { + AddDnsServerCmd cmd = createCmd(); + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetProviderDefault() throws Exception { + AddDnsServerCmd cmd = createCmd(); + setField(cmd, "provider", null); + assertEquals(DnsProviderType.PowerDNS, cmd.getProvider()); + } + + @Test + public void testGetProviderCaseInsensitive() throws Exception { + AddDnsServerCmd cmd = createCmd(); + setField(cmd, "provider", "powerdns"); + assertEquals(DnsProviderType.PowerDNS, cmd.getProvider()); + } + + @Test + public void testExecuteSuccess() throws Exception { + AddDnsServerCmd cmd = createCmd(); + + DnsServer mockServer = mock(DnsServer.class); + DnsServerResponse mockResponse = new DnsServerResponse(); + mockResponse.setName("test-dns"); + + when(dnsProviderManager.addDnsServer(cmd)).thenReturn(mockServer); + when(dnsProviderManager.createDnsServerResponse(mockServer)).thenReturn(mockResponse); + + cmd.execute(); + + DnsServerResponse response = (DnsServerResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("adddnsserverresponse", response.getResponseName()); + verify(dnsProviderManager).addDnsServer(cmd); + verify(dnsProviderManager).createDnsServerResponse(mockServer); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsNull() throws Exception { + AddDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.addDnsServer(cmd)).thenReturn(null); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + AddDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.addDnsServer(cmd)).thenThrow(new RuntimeException("Connection refused")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/AssociateDnsZoneToNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/AssociateDnsZoneToNetworkCmdTest.java new file mode 100644 index 000000000000..593a0a041cea --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/AssociateDnsZoneToNetworkCmdTest.java @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneNetworkMapResponse; +import org.junit.Test; + +import com.cloud.network.Network; +import com.cloud.user.Account; + +public class AssociateDnsZoneToNetworkCmdTest extends BaseDnsCmdTest { + + private static final long NETWORK_ID = 200L; + + private AssociateDnsZoneToNetworkCmd createCmd() throws Exception { + AssociateDnsZoneToNetworkCmd cmd = new AssociateDnsZoneToNetworkCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "_entityMgr", entityManager); + setField(cmd, "dnsZoneId", ENTITY_ID); + setField(cmd, "networkId", NETWORK_ID); + setField(cmd, "subDomain", "dev"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + AssociateDnsZoneToNetworkCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId()); + assertEquals(Long.valueOf(NETWORK_ID), cmd.getNetworkId()); + assertEquals("dev", cmd.getSubDomain()); + } + + @Test + public void testGetEntityOwnerIdWithNetwork() throws Exception { + AssociateDnsZoneToNetworkCmd cmd = createCmd(); + Network mockNetwork = mock(Network.class); + when(mockNetwork.getAccountId()).thenReturn(ACCOUNT_ID); + when(entityManager.findById(Network.class, NETWORK_ID)) + .thenReturn(mockNetwork); + + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetEntityOwnerIdNetworkNotFound() throws Exception { + AssociateDnsZoneToNetworkCmd cmd = createCmd(); + when(entityManager.findById(Network.class, NETWORK_ID)) + .thenReturn(null); + + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } + + @Test + public void testExecuteSuccess() throws Exception { + AssociateDnsZoneToNetworkCmd cmd = createCmd(); + DnsZoneNetworkMapResponse mockResponse = + new DnsZoneNetworkMapResponse(); + when(dnsProviderManager.associateZoneToNetwork(cmd)) + .thenReturn(mockResponse); + + cmd.execute(); + + DnsZoneNetworkMapResponse response = + (DnsZoneNetworkMapResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("associatednszonetonetworkresponse", + response.getResponseName()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + AssociateDnsZoneToNetworkCmd cmd = createCmd(); + when(dnsProviderManager.associateZoneToNetwork(cmd)) + .thenThrow(new RuntimeException("Error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/BaseDnsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/BaseDnsCmdTest.java new file mode 100644 index 000000000000..e8b611ca17ca --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/BaseDnsCmdTest.java @@ -0,0 +1,88 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; + +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.dns.DnsProviderManager; +import org.junit.After; +import org.junit.Before; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import com.cloud.user.Account; +import com.cloud.utils.db.EntityManager; + +/** + * Shared setup for all DNS command unit tests. + */ +public abstract class BaseDnsCmdTest { + + protected static final long ACCOUNT_ID = 42L; + protected static final long ENTITY_ID = 100L; + + protected DnsProviderManager dnsProviderManager; + protected EntityManager entityManager; + protected Account callingAccount; + + private MockedStatic callContextMock; + + @Before + public void setUp() { + dnsProviderManager = mock(DnsProviderManager.class); + entityManager = mock(EntityManager.class); + + callingAccount = mock(Account.class); + when(callingAccount.getId()).thenReturn(ACCOUNT_ID); + + CallContext callContext = mock(CallContext.class); + when(callContext.getCallingAccount()).thenReturn(callingAccount); + + callContextMock = Mockito.mockStatic(CallContext.class); + callContextMock.when(CallContext::current).thenReturn(callContext); + } + + @After + public void tearDown() { + callContextMock.close(); + } + + /** + * Sets a private/inherited field value via reflection. + */ + protected void setField(Object target, String fieldName, Object value) throws Exception { + Field field = null; + Class clazz = target.getClass(); + while (clazz != null) { + try { + field = clazz.getDeclaredField(fieldName); + break; + } catch (NoSuchFieldException e) { + clazz = clazz.getSuperclass(); + } + } + if (field == null) { + throw new NoSuchFieldException(fieldName + " not found in hierarchy of " + target.getClass().getName()); + } + field.setAccessible(true); + field.set(target, value); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmdTest.java new file mode 100644 index 000000000000..c37930dd2289 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/CreateDnsRecordCmdTest.java @@ -0,0 +1,117 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsRecordResponse; +import org.apache.cloudstack.dns.DnsRecord; +import org.junit.Test; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; + +public class CreateDnsRecordCmdTest extends BaseDnsCmdTest { + + private CreateDnsRecordCmd createCmd() throws Exception { + CreateDnsRecordCmd cmd = new CreateDnsRecordCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "dnsZoneId", ENTITY_ID); + setField(cmd, "name", "www"); + setField(cmd, "type", "A"); + setField(cmd, "contents", Arrays.asList("192.168.1.1")); + setField(cmd, "ttl", 7200); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + + assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId()); + assertEquals("www", cmd.getName()); + assertEquals(DnsRecord.RecordType.A, cmd.getType()); + assertEquals(Arrays.asList("192.168.1.1"), cmd.getContents()); + assertEquals(Integer.valueOf(7200), cmd.getTtl()); + } + + @Test + public void testGetTtlDefault() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + setField(cmd, "ttl", null); + assertEquals(Integer.valueOf(3600), cmd.getTtl()); + } + + @Test + public void testGetTypeCname() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + setField(cmd, "type", "CNAME"); + assertEquals(DnsRecord.RecordType.CNAME, cmd.getType()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetTypeInvalid() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + setField(cmd, "type", "INVALID"); + cmd.getType(); + } + + @Test + public void testGetEntityOwnerId() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testEventType() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + assertEquals(EventTypes.EVENT_DNS_RECORD_CREATE, cmd.getEventType()); + } + + @Test + public void testEventDescription() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + assertEquals("Creating DNS Record: www", cmd.getEventDescription()); + } + + @Test + public void testExecuteSuccess() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + + DnsRecordResponse mockResponse = new DnsRecordResponse(); + mockResponse.setName("www"); + when(dnsProviderManager.createDnsRecord(cmd)).thenReturn(mockResponse); + + cmd.execute(); + + DnsRecordResponse response = (DnsRecordResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("creatednsrecordresponse", response.getResponseName()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + CreateDnsRecordCmd cmd = createCmd(); + when(dnsProviderManager.createDnsRecord(cmd)).thenThrow(new RuntimeException("Provider error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmdTest.java new file mode 100644 index 000000000000..dc37874e6599 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/CreateDnsZoneCmdTest.java @@ -0,0 +1,157 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.dns.DnsZone; +import org.junit.Test; + +import com.cloud.event.EventTypes; + +public class CreateDnsZoneCmdTest extends BaseDnsCmdTest { + + private CreateDnsZoneCmd createCmd() throws Exception { + CreateDnsZoneCmd cmd = new CreateDnsZoneCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "name", "example.com"); + setField(cmd, "dnsServerId", ENTITY_ID); + setField(cmd, "type", "Public"); + setField(cmd, "description", "Test zone"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + + assertEquals("example.com", cmd.getName()); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsServerId()); + assertEquals(DnsZone.ZoneType.Public, cmd.getType()); + assertEquals("Test zone", cmd.getDescription()); + } + + @Test + public void testGetTypePrivate() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + setField(cmd, "type", "Private"); + assertEquals(DnsZone.ZoneType.Private, cmd.getType()); + } + + @Test + public void testGetTypeDefaultsToPublicWhenNull() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + setField(cmd, "type", null); + assertEquals(DnsZone.ZoneType.Public, cmd.getType()); + } + + @Test + public void testGetTypeDefaultsToPublicWhenBlank() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + setField(cmd, "type", ""); + assertEquals(DnsZone.ZoneType.Public, cmd.getType()); + } + + @Test + public void testGetEntityOwnerId() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testEventType() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + assertEquals(EventTypes.EVENT_DNS_ZONE_CREATE, cmd.getEventType()); + } + + @Test + public void testEventDescription() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + assertEquals("creating DNS zone: example.com", cmd.getEventDescription()); + } + + @Test + public void testCreateSuccess() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + + DnsZone mockZone = mock(DnsZone.class); + when(mockZone.getId()).thenReturn(ENTITY_ID); + when(mockZone.getUuid()).thenReturn("uuid-123"); + when(dnsProviderManager.allocateDnsZone(cmd)).thenReturn(mockZone); + + cmd.create(); + + assertEquals(Long.valueOf(ENTITY_ID), cmd.getEntityId()); + assertEquals("uuid-123", cmd.getEntityUuid()); + } + + @Test(expected = ServerApiException.class) + public void testCreateReturnsNull() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.allocateDnsZone(cmd)).thenReturn(null); + cmd.create(); + } + + @Test(expected = ServerApiException.class) + public void testCreateThrowsException() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.allocateDnsZone(cmd)).thenThrow(new RuntimeException("DB error")); + cmd.create(); + } + + @Test + public void testExecuteSuccess() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + cmd.setEntityId(ENTITY_ID); + + DnsZone mockZone = mock(DnsZone.class); + DnsZoneResponse mockResponse = new DnsZoneResponse(); + mockResponse.setName("example.com"); + + when(dnsProviderManager.provisionDnsZone(ENTITY_ID, false)).thenReturn(mockZone); + when(dnsProviderManager.createDnsZoneResponse(mockZone)).thenReturn(mockResponse); + + cmd.execute(); + + DnsZoneResponse response = (DnsZoneResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("creatednszoneresponse", response.getResponseName()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsNull() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + cmd.setEntityId(ENTITY_ID); + + when(dnsProviderManager.provisionDnsZone(ENTITY_ID, false)).thenReturn(null); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + CreateDnsZoneCmd cmd = createCmd(); + cmd.setEntityId(ENTITY_ID); + + when(dnsProviderManager.provisionDnsZone(ENTITY_ID, false)).thenThrow(new RuntimeException("Provider error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsRecordCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsRecordCmdTest.java new file mode 100644 index 000000000000..46aacf5a68ce --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsRecordCmdTest.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dns.DnsRecord; +import org.junit.Test; + +import com.cloud.event.EventTypes; +import com.cloud.exception.InvalidParameterValueException; + +public class DeleteDnsRecordCmdTest extends BaseDnsCmdTest { + + private DeleteDnsRecordCmd createCmd() throws Exception { + DeleteDnsRecordCmd cmd = new DeleteDnsRecordCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "dnsZoneId", ENTITY_ID); + setField(cmd, "name", "www"); + setField(cmd, "type", "A"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + + assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId()); + assertEquals("www", cmd.getName()); + assertEquals(DnsRecord.RecordType.A, cmd.getType()); + } + + @Test(expected = InvalidParameterValueException.class) + public void testGetTypeInvalid() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + setField(cmd, "type", "BOGUS"); + cmd.getType(); + } + + @Test + public void testGetEntityOwnerId() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testEventType() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + assertEquals(EventTypes.EVENT_DNS_RECORD_DELETE, cmd.getEventType()); + } + + @Test + public void testEventDescription() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + assertEquals("Deleting DNS Record: www", cmd.getEventDescription()); + } + + @Test + public void testExecuteSuccess() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsRecord(cmd)).thenReturn(true); + + cmd.execute(); + + SuccessResponse response = (SuccessResponse) cmd.getResponseObject(); + assertNotNull(response); + verify(dnsProviderManager).deleteDnsRecord(cmd); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsFalse() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsRecord(cmd)).thenReturn(false); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + DeleteDnsRecordCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsRecord(cmd)).thenThrow(new RuntimeException("Error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmdTest.java new file mode 100644 index 000000000000..adebd339d710 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsServerCmdTest.java @@ -0,0 +1,119 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dns.DnsServer; +import org.junit.Test; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +public class DeleteDnsServerCmdTest extends BaseDnsCmdTest { + + private DeleteDnsServerCmd createCmd() throws Exception { + DeleteDnsServerCmd cmd = new DeleteDnsServerCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "_entityMgr", entityManager); + setField(cmd, "id", ENTITY_ID); + return cmd; + } + + @Test + public void testGetId() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getId()); + } + + @Test + public void testGetCleanupDefault() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + assertTrue(cmd.getCleanup()); + } + + @Test + public void testGetCleanupFalse() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + setField(cmd, "cleanup", false); + assertFalse(cmd.getCleanup()); + } + + @Test + public void testGetEntityOwnerIdWithServer() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + DnsServer mockServer = mock(DnsServer.class); + when(mockServer.getAccountId()).thenReturn(ACCOUNT_ID); + when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(mockServer); + + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetEntityOwnerIdServerNotFound() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(null); + + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } + + @Test + public void testEventType() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + assertEquals(EventTypes.EVENT_DNS_SERVER_DELETE, cmd.getEventType()); + } + + @Test + public void testEventDescription() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + assertEquals("Deleting DNS server ID: " + ENTITY_ID, cmd.getEventDescription()); + } + + @Test + public void testExecuteSuccess() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsServer(cmd)).thenReturn(true); + + cmd.execute(); + + SuccessResponse response = (SuccessResponse) cmd.getResponseObject(); + assertNotNull(response); + verify(dnsProviderManager).deleteDnsServer(cmd); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsFalse() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsServer(cmd)).thenReturn(false); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + DeleteDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsServer(cmd)).thenThrow(new RuntimeException("Error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsZoneCmdTest.java new file mode 100644 index 000000000000..350cea76e5c9 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DeleteDnsZoneCmdTest.java @@ -0,0 +1,104 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.dns.DnsZone; +import org.junit.Test; + +import com.cloud.event.EventTypes; +import com.cloud.user.Account; + +public class DeleteDnsZoneCmdTest extends BaseDnsCmdTest { + + private DeleteDnsZoneCmd createCmd() throws Exception { + DeleteDnsZoneCmd cmd = new DeleteDnsZoneCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "_entityMgr", entityManager); + setField(cmd, "id", ENTITY_ID); + return cmd; + } + + @Test + public void testGetId() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getId()); + } + + @Test + public void testGetEntityOwnerIdWithZone() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + DnsZone mockZone = mock(DnsZone.class); + when(mockZone.getAccountId()).thenReturn(ACCOUNT_ID); + when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(mockZone); + + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetEntityOwnerIdZoneNotFound() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(null); + + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } + + @Test + public void testEventType() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + assertEquals(EventTypes.EVENT_DNS_ZONE_DELETE, cmd.getEventType()); + } + + @Test + public void testEventDescription() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + assertEquals("Deleting DNS Zone ID: " + ENTITY_ID, cmd.getEventDescription()); + } + + @Test + public void testExecuteSuccess() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsZone(ENTITY_ID, false)).thenReturn(true); + + cmd.execute(); + + SuccessResponse response = (SuccessResponse) cmd.getResponseObject(); + assertNotNull(response); + verify(dnsProviderManager).deleteDnsZone(ENTITY_ID, false); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsFalse() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsZone(ENTITY_ID, false)).thenReturn(false); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + DeleteDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.deleteDnsZone(ENTITY_ID, false)).thenThrow(new RuntimeException("Error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DisassociateDnsZoneFromNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DisassociateDnsZoneFromNetworkCmdTest.java new file mode 100644 index 000000000000..34ef07efce3b --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/DisassociateDnsZoneFromNetworkCmdTest.java @@ -0,0 +1,100 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.SuccessResponse; +import org.junit.Test; + +import com.cloud.network.Network; +import com.cloud.user.Account; + +public class DisassociateDnsZoneFromNetworkCmdTest extends BaseDnsCmdTest { + + private static final long NETWORK_ID = 200L; + + private DisassociateDnsZoneFromNetworkCmd createCmd() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = + new DisassociateDnsZoneFromNetworkCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "_entityMgr", entityManager); + setField(cmd, "networkId", NETWORK_ID); + return cmd; + } + + @Test + public void testGetNetworkId() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = createCmd(); + assertEquals(Long.valueOf(NETWORK_ID), cmd.getNetworkId()); + } + + @Test + public void testGetEntityOwnerIdWithNetwork() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = createCmd(); + Network mockNetwork = mock(Network.class); + when(mockNetwork.getAccountId()).thenReturn(ACCOUNT_ID); + when(entityManager.findById(Network.class, NETWORK_ID)) + .thenReturn(mockNetwork); + + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetEntityOwnerIdNetworkNotFound() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = createCmd(); + when(entityManager.findById(Network.class, NETWORK_ID)) + .thenReturn(null); + + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } + + @Test + public void testExecuteSuccess() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = createCmd(); + when(dnsProviderManager.disassociateZoneFromNetwork(cmd)) + .thenReturn(true); + + cmd.execute(); + + SuccessResponse response = + (SuccessResponse) cmd.getResponseObject(); + assertNotNull(response); + verify(dnsProviderManager).disassociateZoneFromNetwork(cmd); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsFalse() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = createCmd(); + when(dnsProviderManager.disassociateZoneFromNetwork(cmd)) + .thenReturn(false); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + DisassociateDnsZoneFromNetworkCmd cmd = createCmd(); + when(dnsProviderManager.disassociateZoneFromNetwork(cmd)) + .thenThrow(new RuntimeException("Error")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsProvidersCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsProvidersCmdTest.java new file mode 100644 index 000000000000..62340742c878 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsProvidersCmdTest.java @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; + +import org.apache.cloudstack.api.response.DnsProviderResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.junit.Test; + +public class ListDnsProvidersCmdTest extends BaseDnsCmdTest { + + private ListDnsProvidersCmd createCmd() throws Exception { + ListDnsProvidersCmd cmd = new ListDnsProvidersCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + return cmd; + } + + @Test + public void testExecute() throws Exception { + ListDnsProvidersCmd cmd = createCmd(); + when(dnsProviderManager.listProviderNames()).thenReturn(Arrays.asList("PowerDNS")); + + cmd.execute(); + + @SuppressWarnings("unchecked") + ListResponse response = + (ListResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("listdnsprovidersresponse", response.getResponseName()); + assertNotNull(response.getResponses()); + assertEquals(1, response.getResponses().size()); + } + + @Test + public void testExecuteMultipleProviders() throws Exception { + ListDnsProvidersCmd cmd = createCmd(); + when(dnsProviderManager.listProviderNames()) + .thenReturn(Arrays.asList("PowerDNS", "Cloudflare")); + + cmd.execute(); + + @SuppressWarnings("unchecked") + ListResponse response = + (ListResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals(2, response.getResponses().size()); + } + + @Test + public void testExecuteEmptyList() throws Exception { + ListDnsProvidersCmd cmd = createCmd(); + when(dnsProviderManager.listProviderNames()) + .thenReturn(Collections.emptyList()); + + cmd.execute(); + + @SuppressWarnings("unchecked") + ListResponse response = + (ListResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals(0, response.getResponses().size()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsRecordsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsRecordsCmdTest.java new file mode 100644 index 000000000000..e75713b55d57 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsRecordsCmdTest.java @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.response.DnsRecordResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.junit.Test; + +public class ListDnsRecordsCmdTest extends BaseDnsCmdTest { + + private ListDnsRecordsCmd createCmd() throws Exception { + ListDnsRecordsCmd cmd = new ListDnsRecordsCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "dnsZoneId", ENTITY_ID); + return cmd; + } + + @Test + public void testGetDnsZoneId() throws Exception { + ListDnsRecordsCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getDnsZoneId()); + } + + @Test + public void testExecute() throws Exception { + ListDnsRecordsCmd cmd = createCmd(); + + ListResponse mockListResponse = new ListResponse<>(); + when(dnsProviderManager.listDnsRecords(cmd)).thenReturn(mockListResponse); + + cmd.execute(); + + @SuppressWarnings("unchecked") + ListResponse response = (ListResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("listdnsrecordsresponse", response.getResponseName()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsServersCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsServersCmdTest.java new file mode 100644 index 000000000000..710aa497167f --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsServersCmdTest.java @@ -0,0 +1,67 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.dns.DnsProviderType; +import org.junit.Test; + +public class ListDnsServersCmdTest extends BaseDnsCmdTest { + + private ListDnsServersCmd createCmd() throws Exception { + ListDnsServersCmd cmd = new ListDnsServersCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "id", ENTITY_ID); + setField(cmd, "providerType", "PowerDNS"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + ListDnsServersCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getId()); + assertEquals(DnsProviderType.PowerDNS, cmd.getProviderType()); + } + + @Test + public void testGetProviderTypeNull() throws Exception { + ListDnsServersCmd cmd = createCmd(); + setField(cmd, "providerType", null); + assertEquals(DnsProviderType.PowerDNS, cmd.getProviderType()); + } + + @Test + public void testExecute() throws Exception { + ListDnsServersCmd cmd = createCmd(); + + ListResponse mockListResponse = new ListResponse<>(); + when(dnsProviderManager.listDnsServers(cmd)).thenReturn(mockListResponse); + + cmd.execute(); + + @SuppressWarnings("unchecked") + ListResponse response = (ListResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("listdnsserversresponse", response.getResponseName()); + assertEquals("dnsserver", response.getObjectName()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsZonesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsZonesCmdTest.java new file mode 100644 index 000000000000..7d47266ae6e6 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/ListDnsZonesCmdTest.java @@ -0,0 +1,60 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.api.response.ListResponse; +import org.junit.Test; + +public class ListDnsZonesCmdTest extends BaseDnsCmdTest { + + private ListDnsZonesCmd createCmd() throws Exception { + ListDnsZonesCmd cmd = new ListDnsZonesCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "id", ENTITY_ID); + setField(cmd, "dnsServerId", 200L); + setField(cmd, "name", "example.com"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + ListDnsZonesCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getId()); + assertEquals(Long.valueOf(200L), cmd.getDnsServerId()); + assertEquals("example.com", cmd.getName()); + } + + @Test + public void testExecute() throws Exception { + ListDnsZonesCmd cmd = createCmd(); + + ListResponse mockListResponse = new ListResponse<>(); + when(dnsProviderManager.listDnsZones(cmd)).thenReturn(mockListResponse); + + cmd.execute(); + + @SuppressWarnings("unchecked") + ListResponse response = (ListResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("listdnszonesresponse", response.getResponseName()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmdTest.java new file mode 100644 index 000000000000..f3c0fcd4fe07 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsServerCmdTest.java @@ -0,0 +1,145 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsServerResponse; +import org.apache.cloudstack.dns.DnsServer; +import org.junit.Test; + +import com.cloud.user.Account; + +public class UpdateDnsServerCmdTest extends BaseDnsCmdTest { + + private UpdateDnsServerCmd createCmd() throws Exception { + UpdateDnsServerCmd cmd = new UpdateDnsServerCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "_entityMgr", entityManager); + setField(cmd, "id", ENTITY_ID); + setField(cmd, "name", "updated-dns"); + setField(cmd, "url", "http://updated.dns.com"); + setField(cmd, "dnsApiKey", "new-api-key"); + setField(cmd, "port", 9090); + setField(cmd, "isPublic", true); + setField(cmd, "publicDomainSuffix", "updated.example.com"); + setField(cmd, "nameServers", Arrays.asList("ns1.updated.com", "ns2.updated.com")); + setField(cmd, "state", "Enabled"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + + assertEquals(Long.valueOf(ENTITY_ID), cmd.getId()); + assertEquals("updated-dns", cmd.getName()); + assertEquals("http://updated.dns.com", cmd.getUrl()); + assertEquals("new-api-key", cmd.getDnsApiKey()); + assertEquals(Integer.valueOf(9090), cmd.getPort()); + assertTrue(cmd.isPublic()); + assertEquals("updated.example.com", cmd.getPublicDomainSuffix()); + assertEquals("ns1.updated.com,ns2.updated.com", cmd.getNameServers()); + assertEquals(DnsServer.State.Enabled, cmd.getState()); + } + + @Test + public void testGetStateDisabled() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + setField(cmd, "state", "Disabled"); + assertEquals(DnsServer.State.Disabled, cmd.getState()); + } + + @Test + public void testGetStateNull() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + setField(cmd, "state", null); + assertNull(cmd.getState()); + } + + @Test + public void testGetStateBlank() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + setField(cmd, "state", ""); + assertNull(cmd.getState()); + } + + @Test(expected = IllegalArgumentException.class) + public void testGetStateInvalid() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + setField(cmd, "state", "InvalidState"); + cmd.getState(); + } + + @Test + public void testGetEntityOwnerIdWithServer() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + DnsServer mockServer = mock(DnsServer.class); + when(mockServer.getAccountId()).thenReturn(ACCOUNT_ID); + when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(mockServer); + + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetEntityOwnerIdServerNotFound() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + when(entityManager.findById(DnsServer.class, ENTITY_ID)).thenReturn(null); + + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } + + @Test + public void testExecuteSuccess() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + + DnsServer mockServer = mock(DnsServer.class); + DnsServerResponse mockResponse = new DnsServerResponse(); + mockResponse.setName("updated-dns"); + + when(dnsProviderManager.updateDnsServer(cmd)).thenReturn(mockServer); + when(dnsProviderManager.createDnsServerResponse(mockServer)).thenReturn(mockResponse); + + cmd.execute(); + + DnsServerResponse response = (DnsServerResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("updatednsserverresponse", response.getResponseName()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsNull() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.updateDnsServer(cmd)).thenReturn(null); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + UpdateDnsServerCmd cmd = createCmd(); + when(dnsProviderManager.updateDnsServer(cmd)).thenThrow(new RuntimeException("Update failed")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmdTest.java new file mode 100644 index 000000000000..cac16903185a --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/dns/UpdateDnsZoneCmdTest.java @@ -0,0 +1,96 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.cloudstack.api.ServerApiException; +import org.apache.cloudstack.api.response.DnsZoneResponse; +import org.apache.cloudstack.dns.DnsZone; +import org.junit.Test; + +import com.cloud.user.Account; + +public class UpdateDnsZoneCmdTest extends BaseDnsCmdTest { + + private UpdateDnsZoneCmd createCmd() throws Exception { + UpdateDnsZoneCmd cmd = new UpdateDnsZoneCmd(); + setField(cmd, "dnsProviderManager", dnsProviderManager); + setField(cmd, "_entityMgr", entityManager); + setField(cmd, "id", ENTITY_ID); + setField(cmd, "description", "Updated description"); + return cmd; + } + + @Test + public void testAccessors() throws Exception { + UpdateDnsZoneCmd cmd = createCmd(); + assertEquals(Long.valueOf(ENTITY_ID), cmd.getId()); + assertEquals("Updated description", cmd.getDescription()); + } + + @Test + public void testGetEntityOwnerIdWhenZoneExists() throws Exception { + UpdateDnsZoneCmd cmd = createCmd(); + DnsZone mockZone = mock(DnsZone.class); + when(mockZone.getAccountId()).thenReturn(ACCOUNT_ID); + when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(mockZone); + assertEquals(ACCOUNT_ID, cmd.getEntityOwnerId()); + } + + @Test + public void testGetEntityOwnerIdWhenZoneNotFound() throws Exception { + UpdateDnsZoneCmd cmd = createCmd(); + when(entityManager.findById(DnsZone.class, ENTITY_ID)).thenReturn(null); + assertEquals(Account.ACCOUNT_ID_SYSTEM, cmd.getEntityOwnerId()); + } + + @Test + public void testExecuteSuccess() throws Exception { + UpdateDnsZoneCmd cmd = createCmd(); + + DnsZone mockZone = mock(DnsZone.class); + DnsZoneResponse mockResponse = new DnsZoneResponse(); + mockResponse.setName("example.com"); + + when(dnsProviderManager.updateDnsZone(cmd)).thenReturn(mockZone); + when(dnsProviderManager.createDnsZoneResponse(mockZone)).thenReturn(mockResponse); + + cmd.execute(); + + DnsZoneResponse response = (DnsZoneResponse) cmd.getResponseObject(); + assertNotNull(response); + assertEquals("updatednszoneresponse", response.getResponseName()); + } + + @Test(expected = ServerApiException.class) + public void testExecuteReturnsNull() throws Exception { + UpdateDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.updateDnsZone(cmd)).thenReturn(null); + cmd.execute(); + } + + @Test(expected = ServerApiException.class) + public void testExecuteThrowsException() throws Exception { + UpdateDnsZoneCmd cmd = createCmd(); + when(dnsProviderManager.updateDnsZone(cmd)).thenThrow(new RuntimeException("Update failed")); + cmd.execute(); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java index c905974b2be9..504d3914b70e 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/firewall/CreateFirewallRuleCmdTest.java @@ -21,10 +21,15 @@ import java.util.Collections; import java.util.List; +import com.cloud.network.IpAddress; +import com.cloud.network.NetworkService; +import com.cloud.utils.db.EntityManager; import org.apache.commons.collections.CollectionUtils; 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; import org.springframework.test.util.ReflectionTestUtils; @@ -33,6 +38,12 @@ @RunWith(MockitoJUnitRunner.class) public class CreateFirewallRuleCmdTest { + @Mock + private EntityManager entityManager; + + @Mock + private NetworkService networkService; + private void validateAllIp4Cidr(final CreateFirewallRuleCmd cmd) { Assert.assertTrue(CollectionUtils.isNotEmpty(cmd.getSourceCidrList())); Assert.assertEquals(1, cmd.getSourceCidrList().size()); @@ -88,4 +99,22 @@ public void testGetSourceCidrList_EmptyFirstElementButMore() { Assert.assertEquals(2, cmd.getSourceCidrList().size()); Assert.assertEquals(cidr, cmd.getSourceCidrList().get(1)); } + + @Test + public void testGetNetworkIdVpcWithoutAssociatedNetworkUsesVpcFallbackAndSyncObjId() { + final CreateFirewallRuleCmd cmd = new CreateFirewallRuleCmd(); + final IpAddress ip = Mockito.mock(IpAddress.class); + + cmd._entityMgr = entityManager; + cmd._networkService = networkService; + ReflectionTestUtils.setField(cmd, "ipAddressId", 42L); + + Mockito.when(networkService.getIp(42L)).thenReturn(ip); + Mockito.when(ip.getAssociatedWithNetworkId()).thenReturn(null); + Mockito.when(ip.getVpcId()).thenReturn(100L); + Mockito.when(ip.getNetworkId()).thenReturn(2L); + + Assert.assertEquals(Long.valueOf(2L), cmd.getNetworkId()); + Assert.assertEquals(Long.valueOf(2L), cmd.getSyncObjId()); + } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmdTest.java new file mode 100644 index 000000000000..54e726eadbe0 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListGpuCardsCmdTest.java @@ -0,0 +1,82 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.gpu; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + + +public class ListGpuCardsCmdTest { + + @Test + public void getId() { + ListGpuCardsCmd cmd = new ListGpuCardsCmd(); + Assert.assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + Assert.assertEquals(id, cmd.getId()); + } + + @Test + public void getVendorName() { + ListGpuCardsCmd cmd = new ListGpuCardsCmd(); + Assert.assertNull(cmd.getVendorName()); + String vendorName = "vendor name"; + ReflectionTestUtils.setField(cmd, "vendorName", vendorName); + Assert.assertEquals(vendorName, cmd.getVendorName()); + } + + @Test + public void getVendorId() { + ListGpuCardsCmd cmd = new ListGpuCardsCmd(); + Assert.assertNull(cmd.getVendorId()); + String vendorId = "vendor id"; + ReflectionTestUtils.setField(cmd, "vendorId", vendorId); + Assert.assertEquals(vendorId, cmd.getVendorId()); + } + + @Test + public void getDeviceId() { + ListGpuCardsCmd cmd = new ListGpuCardsCmd(); + Assert.assertNull(cmd.getDeviceId()); + String deviceId = "device id"; + ReflectionTestUtils.setField(cmd, "deviceId", deviceId); + Assert.assertEquals(deviceId, cmd.getDeviceId()); + } + + @Test + public void getDeviceName() { + ListGpuCardsCmd cmd = new ListGpuCardsCmd(); + Assert.assertNull(cmd.getDeviceName()); + String deviceName = "device name"; + ReflectionTestUtils.setField(cmd, "deviceName", deviceName); + Assert.assertEquals(deviceName, cmd.getDeviceName()); + } + + @Test + public void getActiveOnly() { + ListGpuCardsCmd cmd = new ListGpuCardsCmd(); + Assert.assertFalse(cmd.getActiveOnly()); + Boolean activeOnly = true; + ReflectionTestUtils.setField(cmd, "activeOnly", activeOnly); + Assert.assertEquals(activeOnly, cmd.getActiveOnly()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListGpuDevicesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListGpuDevicesCmdTest.java new file mode 100644 index 000000000000..e1a65ee0ece3 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListGpuDevicesCmdTest.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.gpu; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +public class ListGpuDevicesCmdTest { + + @Test + public void getVmId() { + ListGpuDevicesCmd cmd = new ListGpuDevicesCmd(); + Assert.assertNull(cmd.getVmId()); + Long vmId = 1L; + ReflectionTestUtils.setField(cmd, "vmId", vmId); + Assert.assertEquals(vmId, cmd.getVmId()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmdTest.java new file mode 100644 index 000000000000..7616abd1f8d5 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/gpu/ListVgpuProfilesCmdTest.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.cloudstack.api.command.user.gpu; + +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.util.ReflectionTestUtils; + +public class ListVgpuProfilesCmdTest { + + @Test + public void getId() { + ListVgpuProfilesCmd cmd = new ListVgpuProfilesCmd(); + Assert.assertNull(cmd.getId()); + Long id = 1L; + ReflectionTestUtils.setField(cmd, "id", id); + Assert.assertEquals(id, cmd.getId()); + } + + @Test + public void getName() { + ListVgpuProfilesCmd cmd = new ListVgpuProfilesCmd(); + Assert.assertNull(cmd.getName()); + String name = "Test VGPU Profile"; + ReflectionTestUtils.setField(cmd, "name", name); + Assert.assertEquals(name, cmd.getName()); + } + + @Test + public void getCardId() { + ListVgpuProfilesCmd cmd = new ListVgpuProfilesCmd(); + Assert.assertNull(cmd.getCardId()); + Long cardId = 1L; + ReflectionTestUtils.setField(cmd, "cardId", cardId); + Assert.assertEquals(cardId, cmd.getCardId()); + } + + @Test + public void getActiveOnly() { + ListVgpuProfilesCmd cmd = new ListVgpuProfilesCmd(); + Assert.assertFalse(cmd.getActiveOnly()); + Boolean activeOnly = true; + ReflectionTestUtils.setField(cmd, "activeOnly", activeOnly); + Assert.assertEquals(activeOnly, cmd.getActiveOnly()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmdTest.java new file mode 100644 index 000000000000..f417dc5f876c --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/guest/ListGuestOsCategoriesCmdTest.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.guest; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.cpu.CPU; + +@RunWith(MockitoJUnitRunner.class) +public class ListGuestOsCategoriesCmdTest { + + @Test + public void testIsFeatured() { + ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd(); + Assert.assertNull(cmd.isFeatured()); + ReflectionTestUtils.setField(cmd, "featured", false); + Assert.assertFalse(cmd.isFeatured()); + ReflectionTestUtils.setField(cmd, "featured", true); + Assert.assertTrue(cmd.isFeatured()); + } + + @Test + public void testIsIso() { + ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd(); + Assert.assertNull(cmd.isIso()); + ReflectionTestUtils.setField(cmd, "iso", false); + Assert.assertFalse(cmd.isIso()); + ReflectionTestUtils.setField(cmd, "iso", true); + Assert.assertTrue(cmd.isIso()); + } + + @Test + public void testIsVnf() { + ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd(); + Assert.assertNull(cmd.isVnf()); + ReflectionTestUtils.setField(cmd, "vnf", false); + Assert.assertFalse(cmd.isVnf()); + ReflectionTestUtils.setField(cmd, "vnf", true); + Assert.assertTrue(cmd.isVnf()); + } + + @Test + public void testGetZoneId() { + ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd(); + Assert.assertNull(cmd.getZoneId()); + Long zoneId = 100L; + ReflectionTestUtils.setField(cmd, "zoneId", zoneId); + Assert.assertEquals(zoneId, cmd.getZoneId()); + } + + @Test + public void testGetArch() { + ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd(); + Assert.assertNull(cmd.getArch()); + CPU.CPUArch arch = CPU.CPUArch.getDefault(); + ReflectionTestUtils.setField(cmd, "arch", arch.getType()); + Assert.assertEquals(arch, cmd.getArch()); + } + + @Test + public void testIsShowIcon() { + ListGuestOsCategoriesCmd cmd = new ListGuestOsCategoriesCmd(); + Assert.assertFalse(cmd.isShowIcon()); + ReflectionTestUtils.setField(cmd, "showIcon", false); + Assert.assertFalse(cmd.isShowIcon()); + ReflectionTestUtils.setField(cmd, "showIcon", true); + Assert.assertTrue(cmd.isShowIcon()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmdTest.java index 415ee01ba168..c42a7882c545 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/network/UpdateNetworkCmdTest.java @@ -41,7 +41,10 @@ public class UpdateNetworkCmdTest { NetworkService networkService; @Mock public EntityManager _entityMgr; - private ResponseGenerator responseGenerator; + @Mock + private ResponseGenerator _responseGenerator; + @Mock + private Object job; @InjectMocks UpdateNetworkCmd cmd = new UpdateNetworkCmd(); @@ -176,15 +179,13 @@ public void testExecute() throws InsufficientCapacityException { ReflectionTestUtils.setField(cmd, "id", networkId); ReflectionTestUtils.setField(cmd, "publicMtu", publicmtu); Network network = Mockito.mock(Network.class); - responseGenerator = Mockito.mock(ResponseGenerator.class); NetworkResponse response = Mockito.mock(NetworkResponse.class); response.setPublicMtu(publicmtu); Mockito.when(networkService.getNetwork(networkId)).thenReturn(network); Mockito.when(networkService.updateGuestNetwork(cmd)).thenReturn(network); - cmd._responseGenerator = responseGenerator; - Mockito.when(responseGenerator.createNetworkResponse(ResponseObject.ResponseView.Restricted, network)).thenReturn(response); + Mockito.when(_responseGenerator.createNetworkResponse(ResponseObject.ResponseView.Restricted, network)).thenReturn(response); cmd.execute(); - Mockito.verify(responseGenerator).createNetworkResponse(Mockito.any(ResponseObject.ResponseView.class), Mockito.any(Network.class)); + Mockito.verify(_responseGenerator).createNetworkResponse(Mockito.any(ResponseObject.ResponseView.class), Mockito.any(Network.class)); NetworkResponse actualResponse = (NetworkResponse) cmd.getResponseObject(); Assert.assertEquals(response, actualResponse); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmdTest.java index 2b55d4c6a58d..dbe7669431d8 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/network/routing/DeleteRoutingFirewallRuleCmdTest.java @@ -23,6 +23,7 @@ import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.response.SuccessResponse; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.network.RoutedIpv4Manager; import org.junit.Assert; import org.junit.Test; @@ -31,6 +32,8 @@ import org.mockito.junit.MockitoJUnitRunner; import org.springframework.test.util.ReflectionTestUtils; +import java.util.UUID; + import static org.junit.Assert.assertEquals; @RunWith(MockitoJUnitRunner.class) @@ -46,6 +49,7 @@ public void testProperties() { ReflectionTestUtils.setField(cmd, "_firewallService", _firewallService); long id = 1L; + String uuid = UUID.randomUUID().toString(); long accountId = 2L; long networkId = 3L; @@ -55,12 +59,14 @@ public void testProperties() { Mockito.when(_firewallService.getFirewallRule(id)).thenReturn(firewallRule); ReflectionTestUtils.setField(cmd, "id", id); + CallContext.current().putApiResourceUuid("id", uuid); + assertEquals(id, (long) cmd.getId()); assertEquals(accountId, cmd.getEntityOwnerId()); assertEquals(networkId, (long) cmd.getApiResourceId()); assertEquals(ApiCommandResourceType.Network, cmd.getApiResourceType()); assertEquals(EventTypes.EVENT_ROUTING_IPV4_FIREWALL_RULE_DELETE, cmd.getEventType()); - assertEquals(String.format("Deleting ipv4 routing firewall rule ID=%s", id), cmd.getEventDescription()); + assertEquals(String.format("Deleting IPv4 routing firewall rule with ID: %s", uuid), cmd.getEventDescription()); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmdTest.java index 632496ad2151..db27cc76ec9f 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/CopySnapshotCmdTest.java @@ -87,7 +87,12 @@ public void testGetDestZoneIdWithBothParams() { @Test (expected = ServerApiException.class) public void testExecuteWrongNoParams() { + UUIDManager uuidManager = Mockito.mock(UUIDManager.class); + SnapshotApiService snapshotApiService = Mockito.mock(SnapshotApiService.class); final CopySnapshotCmd cmd = new CopySnapshotCmd(); + cmd._uuidMgr = uuidManager; + cmd._snapshotService = snapshotApiService; + try { cmd.execute(); } catch (ResourceUnavailableException e) { diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmdTest.java new file mode 100644 index 000000000000..36cfcf5cb9a5 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/snapshot/ListSnapshotPoliciesCmdTest.java @@ -0,0 +1,79 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.snapshot; + +import com.cloud.storage.snapshot.SnapshotApiService; +import com.cloud.storage.snapshot.SnapshotPolicy; +import com.cloud.utils.Pair; +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.SnapshotPolicyResponse; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.List; + +public class ListSnapshotPoliciesCmdTest { + private ListSnapshotPoliciesCmd cmd; + private SnapshotApiService snapshotService; + private ResponseGenerator responseGenerator; + + @Before + public void setUp() { + cmd = new ListSnapshotPoliciesCmd(); + snapshotService = Mockito.mock(SnapshotApiService.class); + responseGenerator = Mockito.mock(ResponseGenerator.class); + + cmd._snapshotService = snapshotService; + cmd._responseGenerator = responseGenerator; + } + + @Test + public void testExecuteWithPolicies() { + SnapshotPolicy policy = Mockito.mock(SnapshotPolicy.class); + SnapshotPolicyResponse policyResponse = Mockito.mock(SnapshotPolicyResponse.class); + List policies = new ArrayList<>(); + policies.add(policy); + + Mockito.when(snapshotService.listSnapshotPolicies(cmd)) + .thenReturn(new Pair<>(policies, 1)); + Mockito.when(responseGenerator.createSnapshotPolicyResponse(policy)) + .thenReturn(policyResponse); + + cmd.execute(); + + ListResponse response = (ListResponse) cmd.getResponseObject(); + Assert.assertNotNull(response); + Assert.assertEquals(1, response.getResponses().size()); + Assert.assertEquals(policyResponse, response.getResponses().get(0)); + } + + @Test + public void testExecuteWithNoPolicies() { + Mockito.when(snapshotService.listSnapshotPolicies(cmd)) + .thenReturn(new Pair<>(new ArrayList<>(), 0)); + + cmd.execute(); + + ListResponse response = (ListResponse) cmd.getResponseObject(); + Assert.assertNotNull(response); + Assert.assertTrue(response.getResponses().isEmpty()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmdTest.java index 3f47a078445c..8b7db2924629 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/ListUserDataCmdTest.java @@ -68,7 +68,7 @@ public void testListSuccess() { Pair, Integer> result = new Pair, Integer>(userDataList, 1); UserDataResponse userDataResponse = Mockito.mock(UserDataResponse.class); - Mockito.when(_mgr.listUserDatas(cmd)).thenReturn(result); + Mockito.when(_mgr.listUserDatas(cmd, false)).thenReturn(result); Mockito.when(_responseGenerator.createUserDataResponse(userData)).thenReturn(userDataResponse); cmd.execute(); @@ -82,7 +82,7 @@ public void testEmptyList() { List userDataList = new ArrayList(); Pair, Integer> result = new Pair, Integer>(userDataList, 0); - Mockito.when(_mgr.listUserDatas(cmd)).thenReturn(result); + Mockito.when(_mgr.listUserDatas(cmd, false)).thenReturn(result); cmd.execute(); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmdTest.java index e9605526f86f..8fac32d8f92f 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/userdata/RegisterUserDataCmdTest.java @@ -97,7 +97,7 @@ public void validateArgsCmd() { ReflectionTestUtils.setField(cmd, "name", "testUserdataName"); ReflectionTestUtils.setField(cmd, "userData", "testUserdata"); - when(_accountService.finalyzeAccountId(ACCOUNT_NAME, DOMAIN_ID, PROJECT_ID, true)).thenReturn(200L); + when(_accountService.finalizeAccountId(ACCOUNT_NAME, DOMAIN_ID, PROJECT_ID, true)).thenReturn(200L); Assert.assertEquals("testUserdataName", cmd.getName()); Assert.assertEquals("testUserdata", cmd.getUserData()); diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java index 99bc9d2b3fb7..ae848b2712b1 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/CreateVMScheduleCmdTest.java @@ -21,8 +21,9 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.db.EntityManager; import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -34,9 +35,11 @@ public class CreateVMScheduleCmdTest { @Mock - public VMScheduleManager vmScheduleManager; + public ResourceScheduleManager resourceScheduleManager; + @Mock public EntityManager entityManager; + @InjectMocks private CreateVMScheduleCmd createVMScheduleCmd = new CreateVMScheduleCmd(); @@ -59,10 +62,19 @@ public void tearDown() throws Exception { */ @Test public void testSuccessfulExecution() { - VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class); - Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenReturn(vmScheduleResponse); + ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse(); + scheduleResponse.setId("schedule-uuid"); + scheduleResponse.setResourceId("vm-uuid"); + Mockito.when(resourceScheduleManager.createSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.anyBoolean(), Mockito.any() + )).thenReturn(scheduleResponse); createVMScheduleCmd.execute(); - Assert.assertEquals(vmScheduleResponse, createVMScheduleCmd.getResponseObject()); + VMScheduleResponse response = (VMScheduleResponse) createVMScheduleCmd.getResponseObject(); + Assert.assertNotNull(response); + Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id")); + Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId")); } /** @@ -72,7 +84,11 @@ public void testSuccessfulExecution() { */ @Test(expected = InvalidParameterValueException.class) public void testInvalidParameterValueException() { - Mockito.when(vmScheduleManager.createSchedule(createVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); + Mockito.when(resourceScheduleManager.createSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.anyBoolean(), Mockito.any() + )).thenThrow(new InvalidParameterValueException("Invalid schedule")); createVMScheduleCmd.execute(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java index 1f764a84365f..cec7503bd52b 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeleteVMScheduleCmdTest.java @@ -23,8 +23,7 @@ import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.api.ServerApiException; import org.apache.cloudstack.api.response.SuccessResponse; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -36,7 +35,8 @@ public class DeleteVMScheduleCmdTest { @Mock - public VMScheduleManager vmScheduleManager; + public ResourceScheduleManager resourceScheduleManager; + @Mock public EntityManager entityManager; @@ -64,9 +64,11 @@ public void tearDown() throws Exception { public void testSuccessfulExecution() { final SuccessResponse response = new SuccessResponse(); response.setResponseName(deleteVMScheduleCmd.getCommandName()); - response.setObjectName(VMSchedule.class.getSimpleName().toLowerCase()); + response.setObjectName("vmschedule"); - Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(1L); + Mockito.when(resourceScheduleManager.removeSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenReturn(1L); deleteVMScheduleCmd.execute(); SuccessResponse actualResponse = (SuccessResponse) deleteVMScheduleCmd.getResponseObject(); Assert.assertEquals(response.getResponseName(), actualResponse.getResponseName()); @@ -80,7 +82,9 @@ public void testSuccessfulExecution() { */ @Test(expected = ServerApiException.class) public void testServerApiException() { - Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenReturn(0L); + Mockito.when(resourceScheduleManager.removeSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenReturn(0L); deleteVMScheduleCmd.execute(); } @@ -91,7 +95,9 @@ public void testServerApiException() { */ @Test(expected = InvalidParameterValueException.class) public void testInvalidParameterValueException() { - Mockito.when(vmScheduleManager.removeSchedule(deleteVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); + Mockito.when(resourceScheduleManager.removeSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenThrow(new InvalidParameterValueException("Invalid schedule")); deleteVMScheduleCmd.execute(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmdTest.java new file mode 100644 index 000000000000..09d396e40237 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmdTest.java @@ -0,0 +1,627 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.api.ApiConstants.BootMode; +import org.apache.cloudstack.api.ApiConstants.BootType; +import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; +import org.apache.cloudstack.vm.lease.VMLeaseManager; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.test.util.ReflectionTestUtils; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.network.NetworkService; +import com.cloud.utils.db.EntityManager; +import com.cloud.vm.VmDetailConstants; +import com.cloud.network.Network; +import com.cloud.template.VirtualMachineTemplate; +import com.cloud.offering.DiskOffering; +import com.cloud.network.Network.IpAddresses; +import com.cloud.vm.VmDiskInfo; + +@RunWith(MockitoJUnitRunner.class) +public class DeployVMCmdTest { + + @Spy + private DeployVMCmd cmd = new DeployVMCmd(); + + @Test + public void testGetBootType_ValidUEFI() { + ReflectionTestUtils.setField(cmd, "bootType", "UEFI"); + + BootType result = cmd.getBootType(); + + assertEquals(BootType.UEFI, result); + } + + @Test + public void testGetBootTypeValidBIOS() { + ReflectionTestUtils.setField(cmd, "bootType", "BIOS"); + + BootType result = cmd.getBootType(); + + assertEquals(BootType.BIOS, result); + } + + @Test + public void testGetBootTypeInvalidValue() { + ReflectionTestUtils.setField(cmd, "bootType", "INVALID"); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getBootType(); + }); + assertTrue(thrownException.getMessage().contains("Invalid bootType INVALID")); + } + + @Test + public void testGetBootTypeNullValue() { + ReflectionTestUtils.setField(cmd, "bootType", null); + + BootType result = cmd.getBootType(); + + assertNull(result); + } + + @Test + public void testGetBootModeValidSecure() { + ReflectionTestUtils.setField(cmd, "bootMode", "SECURE"); + ReflectionTestUtils.setField(cmd, "bootType", "UEFI"); + + BootMode result = cmd.getBootMode(); + + assertEquals(BootMode.SECURE, result); + } + + @Test + public void testGetBootModeValidLegacy() { + ReflectionTestUtils.setField(cmd, "bootMode", "LEGACY"); + ReflectionTestUtils.setField(cmd, "bootType", "UEFI"); + + BootMode result = cmd.getBootMode(); + + assertEquals(BootMode.LEGACY, result); + } + + @Test + public void testGetBootModeInvalidValue() { + ReflectionTestUtils.setField(cmd, "bootMode", "INVALID"); + ReflectionTestUtils.setField(cmd, "bootType", "UEFI"); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getBootMode(); + }); + assertTrue(thrownException.getMessage().contains("Invalid bootmode: INVALID specified for VM: null. Valid values are: [LEGACY, SECURE]")); + } + + @Test + public void testGetBootModeUEFIWithoutBootMode() { + ReflectionTestUtils.setField(cmd, "bootMode", null); + ReflectionTestUtils.setField(cmd, "bootType", "UEFI"); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getBootMode(); + }); + assertTrue(thrownException.getMessage().contains("bootmode must be specified for the VM with boot type: UEFI. Valid values are: [LEGACY, SECURE]")); + } + + @Test + public void testGetDetails() { + ReflectionTestUtils.setField(cmd, "bootType", "UEFI"); + ReflectionTestUtils.setField(cmd, "bootMode", "SECURE"); + ReflectionTestUtils.setField(cmd, "rootdisksize", 100L); + ReflectionTestUtils.setField(cmd, "ioDriverPolicy", "native"); + ReflectionTestUtils.setField(cmd, "iothreadsEnabled", true); + ReflectionTestUtils.setField(cmd, "nicMultiqueueNumber", null); + ReflectionTestUtils.setField(cmd, "nicPackedVirtQueues", null); + ReflectionTestUtils.setField(cmd, "details", new HashMap<>()); + + Map result = cmd.getDetails(); + + assertEquals("SECURE", result.get("UEFI")); + assertEquals("100", result.get(VmDetailConstants.ROOT_DISK_SIZE)); + assertEquals("native", result.get(VmDetailConstants.IO_POLICY)); + assertEquals("true", result.get(VmDetailConstants.IOTHREADS)); + } + + @Test + public void testGetLeaseExpiryActionValidStop() { + ReflectionTestUtils.setField(cmd, "leaseExpiryAction", "STOP"); + + VMLeaseManager.ExpiryAction result = cmd.getLeaseExpiryAction(); + + assertEquals(VMLeaseManager.ExpiryAction.STOP, result); + } + + @Test + public void testGetLeaseExpiryActionValidDestroy() { + ReflectionTestUtils.setField(cmd, "leaseExpiryAction", "DESTROY"); + + VMLeaseManager.ExpiryAction result = cmd.getLeaseExpiryAction(); + + assertEquals(VMLeaseManager.ExpiryAction.DESTROY, result); + } + + @Test + public void testGetLeaseExpiryActionInvalidValue() { + ReflectionTestUtils.setField(cmd, "leaseExpiryAction", "INVALID"); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getLeaseExpiryAction(); + }); + assertTrue(thrownException.getMessage().contains("Invalid value configured for leaseexpiryaction")); + } + + @Test + public void testGetLeaseExpiryActionNullValue() { + ReflectionTestUtils.setField(cmd, "leaseExpiryAction", null); + + VMLeaseManager.ExpiryAction result = cmd.getLeaseExpiryAction(); + + assertNull(result); + } + + @Test + public void testGetIoDriverPolicyValidThrottle() { + ReflectionTestUtils.setField(cmd, "ioDriverPolicy", "native"); + + IoDriverPolicy result = cmd.getIoDriverPolicy(); + + assertEquals(IoDriverPolicy.valueOf("NATIVE"), result); + } + + @Test + public void testGetIoDriverPolicyInvalidValue() { + ReflectionTestUtils.setField(cmd, "ioDriverPolicy", "INVALID"); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getIoDriverPolicy(); + }); + assertTrue(thrownException.getMessage().contains("Invalid io policy INVALID")); + } + + @Test + public void testGetNetworkIds() { + List networkIds = Arrays.asList(1L, 2L, 3L); + ReflectionTestUtils.setField(cmd, "networkIds", networkIds); + ReflectionTestUtils.setField(cmd, "vAppNetworks", null); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", null); + + List result = cmd.getNetworkIds(); + + assertEquals(networkIds, result); + } + + @Test + public void testGetNetworkIdsVAppNetworks() { + Map vAppNetworks = new HashMap<>(); + vAppNetworks.put("network1", new HashMap()); + ReflectionTestUtils.setField(cmd, "vAppNetworks", vAppNetworks); + ReflectionTestUtils.setField(cmd, "networkIds", null); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", null); + ReflectionTestUtils.setField(cmd, "ipAddress", null); + ReflectionTestUtils.setField(cmd, "ip6Address", null); + + List result = cmd.getNetworkIds(); + + assertTrue(result.isEmpty()); + } + + @Test + public void testGetNetworkIdsVAppNetworksAndNetworkIds() { + Map vAppNetworks = new HashMap<>(); + vAppNetworks.put("network1", new HashMap()); + ReflectionTestUtils.setField(cmd, "vAppNetworks", vAppNetworks); + ReflectionTestUtils.setField(cmd, "networkIds", Arrays.asList(1L, 2L)); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getNetworkIds(); + }); + assertTrue(thrownException.getMessage().contains("nicnetworklist can't be specified along with networkids")); + } + + @Test + public void testGetNetworkIdsIpToNetworkListAndNetworkIds() { + Map ipToNetworkList = new HashMap<>(); + ipToNetworkList.put("0", new HashMap()); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", ipToNetworkList); + ReflectionTestUtils.setField(cmd, "networkIds", Arrays.asList(1L, 2L)); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getNetworkIds(); + }); + assertTrue(thrownException.getMessage().contains("ipToNetworkMap can't be specified along with networkIds or ipAddress")); + } + + @Test + public void testGetIpToNetworkMap_WithNetworkIds() { + ReflectionTestUtils.setField(cmd, "networkIds", Arrays.asList(1L, 2L)); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", new HashMap<>()); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getIpToNetworkMap(); + }); + assertTrue(thrownException.getMessage().contains("NetworkIds and ipAddress can't be specified along with ipToNetworkMap parameter")); + } + + @Test + public void testGetIpToNetworkMap_WithIpAddress() { + ReflectionTestUtils.setField(cmd, "ipAddress", "192.168.1.1"); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", new HashMap<>()); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getIpToNetworkMap(); + }); + assertTrue(thrownException.getMessage().contains("NetworkIds and ipAddress can't be specified along with ipToNetworkMap parameter")); + } + + @Test + public void testGetIpToNetworkMap_WithEmptyIpToNetworkList() { + ReflectionTestUtils.setField(cmd, "networkIds", null); + ReflectionTestUtils.setField(cmd, "ipAddress", null); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", new HashMap<>()); + + Map result = cmd.getIpToNetworkMap(); + + assertNull(result); + } + + @Test + public void testGetIpToNetworkMap_WithNullIpToNetworkList() { + ReflectionTestUtils.setField(cmd, "networkIds", null); + ReflectionTestUtils.setField(cmd, "ipAddress", null); + ReflectionTestUtils.setField(cmd, "ipToNetworkList", null); + + Map result = cmd.getIpToNetworkMap(); + + assertNull(result); + } + + @Test + public void testGetDataDiskInfoList() { + Map dataDisksDetails = new HashMap<>(); + Map dataDisk = new HashMap<>(); + dataDisk.put(ApiConstants.DISK_OFFERING_ID, "offering-uuid"); + dataDisk.put(ApiConstants.DEVICE_ID, "0"); + dataDisk.put(ApiConstants.MIN_IOPS, "1000"); + dataDisk.put(ApiConstants.MAX_IOPS, "2000"); + dataDisksDetails.put("0", dataDisk); + + ReflectionTestUtils.setField(cmd, "dataDisksDetails", dataDisksDetails); + + EntityManager entityMgr = mock(EntityManager.class); + ReflectionTestUtils.setField(cmd, "_entityMgr", entityMgr); + DiskOffering diskOffering = mock(DiskOffering.class); + when(diskOffering.getDiskSize()).thenReturn(1024 * 1024 * 1024L); + when(diskOffering.isCustomizedIops()).thenReturn(true); + when(entityMgr.findByUuid(DiskOffering.class, "offering-uuid")).thenReturn(diskOffering); + + List result = cmd.getDataDiskInfoList(); + + assertNotNull(result); + assertEquals(1, result.size()); + assertEquals(diskOffering, result.get(0).getDiskOffering()); + assertEquals(1L, result.get(0).getSize().longValue()); + assertEquals(1000L, result.get(0).getMinIops().longValue()); + assertEquals(2000L, result.get(0).getMaxIops().longValue()); + } + + @Test + public void testGetIpAddressesFromIpMap() { + Map ipToNetworkList = new HashMap<>(); + Map ipMap = new HashMap<>(); + ipMap.put("ip", "192.168.1.100"); + ipMap.put("mac", "00:11:22:33:44:55"); + ipMap.put("networkid", "1"); + ipToNetworkList.put("0", ipMap); + + ReflectionTestUtils.setField(cmd, "ipToNetworkList", ipToNetworkList); + ReflectionTestUtils.setField(cmd, "networkIds", null); + ReflectionTestUtils.setField(cmd, "ipAddress", null); + + Network mockNetwork = mock(Network.class); + NetworkService networkServiceMock = mock(NetworkService.class); + ReflectionTestUtils.setField(cmd, "_networkService", networkServiceMock); + + Map result = cmd.getIpToNetworkMap(); + + assertNotNull(result); + assertTrue(result.containsKey(1L)); + assertEquals(result.get(1L).getIp4Address(), "192.168.1.100"); + assertEquals(result.get(1L).getMacAddress(), "00:11:22:33:44:55"); + } + + @Test + public void testGetIpAddressesFromIpMapInvalidMac() { + Map ipToNetworkList = new HashMap<>(); + Map ipMap = new HashMap<>(); + ipMap.put("ip", "192.168.1.100"); + ipMap.put("mac", "invalid-mac"); + ipMap.put("networkid", "1"); + ipToNetworkList.put("0", ipMap); + + ReflectionTestUtils.setField(cmd, "ipToNetworkList", ipToNetworkList); + ReflectionTestUtils.setField(cmd, "networkIds", null); + ReflectionTestUtils.setField(cmd, "ipAddress", null); + + Network mockNetwork = mock(Network.class); + NetworkService networkServiceMock = mock(NetworkService.class); + ReflectionTestUtils.setField(cmd, "_networkService", networkServiceMock); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getIpToNetworkMap(); + }); + assertTrue(thrownException.getMessage().contains("Mac address is not valid")); + } + + @Test + public void testGetDhcpOptionsMap() { + Map dhcpOptionsNetworkList = new HashMap<>(); + Map dhcpOptions = new HashMap<>(); + dhcpOptions.put("networkid", "network-1"); + dhcpOptions.put("dhcp:114", "url-value"); + dhcpOptions.put("dhcp:66", "www.test.com"); + dhcpOptionsNetworkList.put("0", dhcpOptions); + + ReflectionTestUtils.setField(cmd, "dhcpOptionsNetworkList", dhcpOptionsNetworkList); + + Map> result = cmd.getDhcpOptionsMap(); + + assertNotNull(result); + assertTrue(result.containsKey("network-1")); + Map networkOptions = result.get("network-1"); + assertEquals("url-value", networkOptions.get(114)); + assertEquals("www.test.com", networkOptions.get(66)); + } + + @Test + public void testGetDhcpOptionsMap_WithMissingNetworkId() { + Map dhcpOptionsNetworkList = new HashMap<>(); + Map dhcpOptions = new HashMap<>(); + dhcpOptions.put("dhcp:114", "url-value"); + dhcpOptionsNetworkList.put("0", dhcpOptions); + + ReflectionTestUtils.setField(cmd, "dhcpOptionsNetworkList", dhcpOptionsNetworkList); + + IllegalArgumentException thrownException = assertThrows(IllegalArgumentException.class, () -> { + cmd.getDhcpOptionsMap(); + }); + assertTrue(thrownException.getMessage().contains("No networkid specified when providing extra dhcp options")); + } + + @Test + public void testGetDataDiskTemplateToDiskOfferingMap() { + ReflectionTestUtils.setField(cmd, "diskOfferingId", null); + + Map dataDiskTemplateToDiskOfferingList = new HashMap<>(); + Map dataDiskTemplate = new HashMap<>(); + dataDiskTemplate.put("datadisktemplateid", "template-uuid"); + dataDiskTemplate.put("diskofferingid", "offering-uuid"); + dataDiskTemplateToDiskOfferingList.put("0", dataDiskTemplate); + + ReflectionTestUtils.setField(cmd, "dataDiskTemplateToDiskOfferingList", dataDiskTemplateToDiskOfferingList); + + VirtualMachineTemplate mockTemplate = mock(VirtualMachineTemplate.class); + when(mockTemplate.getId()).thenReturn(1L); + + DiskOffering mockOffering = mock(DiskOffering.class); + + EntityManager entityMgr = mock(EntityManager.class); + ReflectionTestUtils.setField(cmd, "_entityMgr", entityMgr); + when(entityMgr.findByUuid(VirtualMachineTemplate.class, "template-uuid")).thenReturn(mockTemplate); + when(entityMgr.findByUuid(DiskOffering.class, "offering-uuid")).thenReturn(mockOffering); + + Map result = cmd.getDataDiskTemplateToDiskOfferingMap(); + + assertNotNull(result); + assertEquals(mockOffering, result.get(1L)); + } + + @Test + public void testGetDataDiskTemplateToDiskOfferingMapWithDiskOfferingId() { + ReflectionTestUtils.setField(cmd, "diskOfferingId", 1L); + ReflectionTestUtils.setField(cmd, "dataDiskTemplateToDiskOfferingList", new HashMap<>()); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getDataDiskTemplateToDiskOfferingMap(); + }); + assertTrue(thrownException.getMessage().contains("diskofferingid parameter can't be specified along with datadisktemplatetodiskofferinglist parameter")); + } + + @Test + public void testGetDataDiskTemplateToDiskOfferingMapInvalidTemplateId() { + ReflectionTestUtils.setField(cmd, "diskOfferingId", null); + + Map dataDiskTemplateToDiskOfferingList = new HashMap<>(); + Map dataDiskTemplate = new HashMap<>(); + dataDiskTemplate.put("datadisktemplateid", "invalid-template"); + dataDiskTemplate.put("diskofferingid", "offering-uuid"); + dataDiskTemplateToDiskOfferingList.put("0", dataDiskTemplate); + + ReflectionTestUtils.setField(cmd, "dataDiskTemplateToDiskOfferingList", dataDiskTemplateToDiskOfferingList); + + EntityManager entityMgr = mock(EntityManager.class); + ReflectionTestUtils.setField(cmd, "_entityMgr", entityMgr); + when(entityMgr.findByUuid(VirtualMachineTemplate.class, "invalid-template")).thenReturn(null); + when(entityMgr.findById(VirtualMachineTemplate.class, "invalid-template")).thenReturn(null); + + InvalidParameterValueException thrownException = assertThrows(InvalidParameterValueException.class, () -> { + cmd.getDataDiskTemplateToDiskOfferingMap(); + }); + assertTrue(thrownException.getMessage().contains("Unable to translate and find entity with datadisktemplateid")); + } + + @Test + public void testSetServiceOfferingId() { + cmd.setServiceOfferingId(101L); + assertEquals(Long.valueOf(101L), cmd.getServiceOfferingId()); + } + + @Test + public void testSetTemplateId() { + cmd.setTemplateId(102L); + assertEquals(Long.valueOf(102L), cmd.getTemplateId()); + } + + @Test + public void testSetVolumeId() { + cmd.setVolumeId(103L); + assertEquals(Long.valueOf(103L), cmd.getVolumeId()); + } + + @Test + public void testSetSnapshotId() { + cmd.setSnapshotId(104L); + assertEquals(Long.valueOf(104L), cmd.getSnapshotId()); + } + + @Test + public void testSetZoneId() { + cmd.setZoneId(105L); + assertEquals(Long.valueOf(105L), cmd.getZoneId()); + } + + @Test + public void testSetName() { + cmd.setName("vm-name"); + assertEquals("vm-name", cmd.getName()); + } + + @Test + public void testSetDisplayName() { + cmd.setDisplayName("vm-display-name"); + assertEquals("vm-display-name", cmd.getDisplayName()); + } + + @Test + public void testSetAccountName() { + cmd.setAccountName("account-name"); + assertEquals("account-name", cmd.getAccountName()); + } + + @Test + public void testSetDomainId() { + cmd.setDomainId(106L); + assertEquals(Long.valueOf(106L), cmd.getDomainId()); + } + + @Test + public void testSetNetworkIds() { + List networkIds = Arrays.asList(11L, 12L); + cmd.setNetworkIds(networkIds); + assertEquals(networkIds, cmd.getNetworkIds()); + } + + @Test + public void testSetBootType() { + cmd.setBootType("UEFI"); + assertEquals(BootType.UEFI, cmd.getBootType()); + } + + @Test + public void testSetBootMode() { + cmd.setBootType("UEFI"); + cmd.setBootMode("SECURE"); + assertEquals(BootMode.SECURE, cmd.getBootMode()); + } + + @Test + public void testSetHypervisor() { + cmd.setHypervisor("KVM"); + assertEquals(HypervisorType.KVM, cmd.getHypervisor()); + } + + @Test + public void testSetUserData() { + cmd.setUserData("dXNlci1kYXRh"); + assertEquals("dXNlci1kYXRh", cmd.getUserData()); + } + + @Test + public void testSetKeyboard() { + cmd.setKeyboard("us"); + assertEquals("us", cmd.getKeyboard()); + } + + @Test + public void testSetProjectId() { + cmd.setProjectId(107L); + assertEquals(Long.valueOf(107L), ReflectionTestUtils.getField(cmd, "projectId")); + } + + @Test + public void testSetDisplayVm() { + cmd.setDisplayVm(Boolean.FALSE); + assertEquals(Boolean.FALSE, cmd.isDisplayVm()); + } + + @Test + public void testSetUserDataId() { + cmd.setUserDataId(108L); + assertEquals(Long.valueOf(108L), cmd.getUserdataId()); + } + + @Test + public void testSetAffinityGroupIds() { + List affinityGroupIds = Arrays.asList(21L, 22L); + cmd.setAffinityGroupIds(affinityGroupIds); + assertEquals(affinityGroupIds, cmd.getAffinityGroupIdList()); + } + + @Test + public void testSetDetails() { + Map details = new HashMap<>(); + details.put("key", "value"); + cmd.setDetails(details); + assertEquals(details, ReflectionTestUtils.getField(cmd, "details")); + } + + @Test + public void testSetExtraConfig() { + cmd.setExtraConfig("cpu-mode=host-passthrough"); + assertEquals("cpu-mode=host-passthrough", cmd.getExtraConfig()); + } + + @Test + public void testSetDynamicScalingEnabled() { + cmd.setDynamicScalingEnabled(Boolean.FALSE); + assertFalse(cmd.isDynamicScalingEnabled()); + } + + @Test + public void testIsBlankInstance() { + assertFalse(cmd.isBlankInstance()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java index f5434de3581c..5449be17a576 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMScheduleCmdTest.java @@ -20,8 +20,9 @@ import com.cloud.exception.InvalidParameterValueException; import org.apache.cloudstack.api.response.ListResponse; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -36,7 +37,8 @@ public class ListVMScheduleCmdTest { @Mock - public VMScheduleManager vmScheduleManager; + public ResourceScheduleManager resourceScheduleManager; + @InjectMocks private ListVMScheduleCmd listVMScheduleCmd = new ListVMScheduleCmd(); private AutoCloseable closeable; @@ -58,12 +60,14 @@ public void tearDown() throws Exception { */ @Test public void testEmptyResponse() { - ListResponse response = new ListResponse(); - response.setResponses(new ArrayList()); - Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(response); + ListResponse response = new ListResponse(); + response.setResponses(new ArrayList()); + Mockito.when(resourceScheduleManager.listSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenReturn(response); listVMScheduleCmd.execute(); ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject(); - Assert.assertEquals(response, actualResponseObject); Assert.assertEquals(0L, actualResponseObject.getResponses().size()); } @@ -74,15 +78,22 @@ public void testEmptyResponse() { */ @Test public void testNonEmptyResponse() { - ListResponse listResponse = new ListResponse(); - VMScheduleResponse response = Mockito.mock(VMScheduleResponse.class); + ListResponse listResponse = new ListResponse(); + ResourceScheduleResponse response = new ResourceScheduleResponse(); + response.setId("schedule-uuid"); + response.setResourceId("vm-uuid"); listResponse.setResponses(Collections.singletonList(response)); - Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenReturn(listResponse); + Mockito.when(resourceScheduleManager.listSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenReturn(listResponse); listVMScheduleCmd.execute(); ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject(); - Assert.assertEquals(listResponse, actualResponseObject); Assert.assertEquals(1L, actualResponseObject.getResponses().size()); - Assert.assertEquals(response, actualResponseObject.getResponses().get(0)); + Assert.assertEquals("schedule-uuid", + org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "id")); + Assert.assertEquals("vm-uuid", + org.springframework.test.util.ReflectionTestUtils.getField(actualResponseObject.getResponses().get(0), "vmId")); } /** @@ -92,7 +103,10 @@ public void testNonEmptyResponse() { */ @Test(expected = InvalidParameterValueException.class) public void testInvalidParameterValueException() { - Mockito.when(vmScheduleManager.listSchedule(listVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); + Mockito.when(resourceScheduleManager.listSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenThrow(InvalidParameterValueException.class); listVMScheduleCmd.execute(); ListResponse actualResponseObject = (ListResponse) listVMScheduleCmd.getResponseObject(); } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmdTest.java new file mode 100644 index 000000000000..48b41a47fff3 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/ListVMsCmdTest.java @@ -0,0 +1,223 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.command.user.vm; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.anyList; +import static org.mockito.Mockito.anySet; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import org.apache.cloudstack.api.ResponseGenerator; +import org.apache.cloudstack.api.response.ResourceIconResponse; +import org.apache.cloudstack.api.response.UserVmResponse; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import com.cloud.server.ResourceIcon; +import com.cloud.server.ResourceIconManager; +import com.cloud.server.ResourceTag; +import com.cloud.storage.GuestOS; +import com.cloud.utils.db.EntityManager; + +public class ListVMsCmdTest { + + EntityManager _entityMgr; + ResourceIconManager resourceIconManager; + ResponseGenerator _responseGenerator; + + ListVMsCmd cmd; + + @Before + public void setup() { + _entityMgr = mock(EntityManager.class); + resourceIconManager = mock(ResourceIconManager.class); + _responseGenerator = mock(ResponseGenerator.class); + cmd = spy(ListVMsCmd.class); + cmd._entityMgr = _entityMgr; + cmd.resourceIconManager = resourceIconManager; + cmd._responseGenerator = _responseGenerator; + } + + @Test + public void testUpdateVMResponse_withMixedIcons() { + String vm1Uuid = UUID.randomUUID().toString(); + UserVmResponse vm1 = mock(UserVmResponse.class); + when(vm1.getId()).thenReturn(vm1Uuid); + String vm2Uuid = UUID.randomUUID().toString(); + UserVmResponse vm2 = mock(UserVmResponse.class); + when(vm2.getId()).thenReturn(vm2Uuid); + List responses = Arrays.asList(vm1, vm2); + ResourceIcon icon1 = mock(ResourceIcon.class); + ResourceIcon icon2 = mock(ResourceIcon.class); + Map initialIcons = new HashMap<>(); + initialIcons.put(vm1Uuid, icon1); + when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.UserVm, Set.of(vm1Uuid, vm2Uuid))) + .thenReturn(initialIcons); + Map fallbackIcons = Map.of(vm2Uuid, icon2); + doReturn(fallbackIcons).when(cmd).getResourceIconsForUsingTemplateIso(anyList()); + ResourceIconResponse iconResponse1 = new ResourceIconResponse(); + ResourceIconResponse iconResponse2 = new ResourceIconResponse(); + when(_responseGenerator.createResourceIconResponse(icon1)).thenReturn(iconResponse1); + when(_responseGenerator.createResourceIconResponse(icon2)).thenReturn(iconResponse2); + cmd.updateVMResponse(responses); + verify(vm1).setResourceIconResponse(iconResponse1); + verify(vm2).setResourceIconResponse(iconResponse2); + } + + @Test + public void testUpdateVMResponse_withEmptyList() { + cmd.updateVMResponse(Collections.emptyList()); + verify(resourceIconManager, never()).getByResourceTypeAndIds(Mockito.any(), Mockito.anyCollection()); + } + + @Test + public void testGetResourceIconsForUsingTemplateIso_withValidData() { + String vm1Uuid = UUID.randomUUID().toString(); + String template1Uuid = UUID.randomUUID().toString(); + UserVmResponse vm1 = mock(UserVmResponse.class); + when(vm1.getId()).thenReturn(vm1Uuid); + when(vm1.getTemplateId()).thenReturn(template1Uuid); + when(vm1.getIsoId()).thenReturn(null); + String vm2Uuid = UUID.randomUUID().toString(); + String iso2Uuid = UUID.randomUUID().toString(); + UserVmResponse vm2 = mock(UserVmResponse.class); + when(vm2.getId()).thenReturn(vm2Uuid); + when(vm2.getTemplateId()).thenReturn(null); + when(vm2.getIsoId()).thenReturn(iso2Uuid); + List responses = Arrays.asList(vm1, vm2); + Map templateIcons = new HashMap<>(); + templateIcons.put(template1Uuid, mock(ResourceIcon.class)); + Map isoIcons = new HashMap<>(); + isoIcons.put(iso2Uuid, mock(ResourceIcon.class)); + when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.Template, Set.of(template1Uuid))) + .thenReturn(templateIcons); + when(resourceIconManager.getByResourceTypeAndUuids(ResourceTag.ResourceObjectType.ISO, Set.of(iso2Uuid))) + .thenReturn(isoIcons); + doReturn(Collections.emptyMap()).when(cmd).getResourceIconsUsingOsCategory(anyList()); + Map result = cmd.getResourceIconsForUsingTemplateIso(responses); + assertEquals(2, result.size()); + assertTrue(result.containsKey(vm1Uuid)); + assertTrue(result.containsKey(vm2Uuid)); + assertEquals(templateIcons.get(template1Uuid), result.get(vm1Uuid)); + assertEquals(isoIcons.get(iso2Uuid), result.get(vm2Uuid)); + } + + @Test + public void testGetResourceIconsForUsingTemplateIso_withMissingIcons() { + String vm1Uuid = UUID.randomUUID().toString(); + String template1Uuid = UUID.randomUUID().toString(); + UserVmResponse vm1 = mock(UserVmResponse.class); + when(vm1.getId()).thenReturn(vm1Uuid); + when(vm1.getTemplateId()).thenReturn(template1Uuid); + when(vm1.getIsoId()).thenReturn(null); + List responses = List.of(vm1); + when(resourceIconManager.getByResourceTypeAndUuids(eq(ResourceTag.ResourceObjectType.Template), anySet())) + .thenReturn(Collections.emptyMap()); + when(resourceIconManager.getByResourceTypeAndUuids(eq(ResourceTag.ResourceObjectType.ISO), anySet())) + .thenReturn(Collections.emptyMap()); + Map fallbackIcons = Map.of(vm1Uuid, mock(ResourceIcon.class)); + doReturn(fallbackIcons).when(cmd).getResourceIconsUsingOsCategory(anyList()); + Map result = cmd.getResourceIconsForUsingTemplateIso(responses); + assertEquals(1, result.size()); + assertEquals(fallbackIcons.get("vm1"), result.get("vm1")); + } + + @Test + public void testGetResourceIconsUsingOsCategory_withValidData() { + String vm1Uuid = UUID.randomUUID().toString(); + String os1Uuid = UUID.randomUUID().toString(); + UserVmResponse vm1 = mock(UserVmResponse.class); + when(vm1.getGuestOsId()).thenReturn(os1Uuid); + when(vm1.getId()).thenReturn(vm1Uuid); + String vm2Uuid = UUID.randomUUID().toString(); + String os2Uuid = UUID.randomUUID().toString(); + UserVmResponse vm2 = mock(UserVmResponse.class); + when(vm2.getGuestOsId()).thenReturn(os2Uuid); + when(vm2.getId()).thenReturn(vm2Uuid); + List responses = Arrays.asList(vm1, vm2); + GuestOS guestOS1 = mock(GuestOS.class); + when(guestOS1.getUuid()).thenReturn(os1Uuid); + when(guestOS1.getCategoryId()).thenReturn(10L); + GuestOS guestOS2 = mock(GuestOS.class); + when(guestOS2.getUuid()).thenReturn(os2Uuid); + when(guestOS2.getCategoryId()).thenReturn(20L); + when(_entityMgr.listByUuids(eq(GuestOS.class), anySet())) + .thenReturn(Arrays.asList(guestOS1, guestOS2)); + ResourceIcon icon1 = mock(ResourceIcon.class); + ResourceIcon icon2 = mock(ResourceIcon.class); + Map categoryIcons = new HashMap<>(); + categoryIcons.put(10L, icon1); + categoryIcons.put(20L, icon2); + when(resourceIconManager.getByResourceTypeAndIds(eq(ResourceTag.ResourceObjectType.GuestOsCategory), anySet())) + .thenReturn(categoryIcons); + Map result = cmd.getResourceIconsUsingOsCategory(responses); + assertEquals(2, result.size()); + assertEquals(icon1, result.get(vm1Uuid)); + assertEquals(icon2, result.get(vm2Uuid)); + } + + @Test + public void testGetResourceIconsUsingOsCategory_missingGuestOS() { + String vm1Uuid = UUID.randomUUID().toString(); + String os1Uuid = UUID.randomUUID().toString(); + UserVmResponse vm1 = mock(UserVmResponse.class); + when(vm1.getGuestOsId()).thenReturn(vm1Uuid); + when(vm1.getId()).thenReturn(os1Uuid); + List responses = Collections.singletonList(vm1); + when(_entityMgr.listByUuids(eq(GuestOS.class), anySet())) + .thenReturn(Collections.emptyList()); + Map result = cmd.getResourceIconsUsingOsCategory(responses); + assertTrue(result.isEmpty()); + } + + @Test + public void testGetResourceIconsUsingOsCategory_missingIcon() { + UserVmResponse vm1 = mock(UserVmResponse.class); + String vmUuid = UUID.randomUUID().toString(); + String osUuid = UUID.randomUUID().toString(); + when(vm1.getGuestOsId()).thenReturn(osUuid); + when(vm1.getId()).thenReturn(vmUuid); + List responses = Collections.singletonList(vm1); + GuestOS guestOS1 = mock(GuestOS.class); + when(guestOS1.getCategoryId()).thenReturn(10L); + when(guestOS1.getUuid()).thenReturn(osUuid); + when(_entityMgr.listByUuids(eq(GuestOS.class), anySet())) + .thenReturn(Collections.singletonList(guestOS1)); + when(resourceIconManager.getByResourceTypeAndIds(eq(ResourceTag.ResourceObjectType.GuestOsCategory), anySet())) + .thenReturn(Collections.emptyMap()); + Map result = cmd.getResourceIconsUsingOsCategory(responses); + assertTrue(result.containsKey(vmUuid)); + assertNull(result.get(vmUuid)); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java index 2c6c485f25bf..a24252f4a1d0 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vm/UpdateVMScheduleCmdTest.java @@ -21,9 +21,11 @@ import com.cloud.exception.InvalidParameterValueException; import com.cloud.utils.db.EntityManager; import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.response.ResourceScheduleResponse; import org.apache.cloudstack.api.response.VMScheduleResponse; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleManager; +import org.apache.cloudstack.schedule.ResourceSchedule; +import org.apache.cloudstack.schedule.ResourceScheduleManager; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -35,9 +37,11 @@ public class UpdateVMScheduleCmdTest { @Mock - public VMScheduleManager vmScheduleManager; + public ResourceScheduleManager resourceScheduleManager; + @Mock public EntityManager entityManager; + @InjectMocks private UpdateVMScheduleCmd updateVMScheduleCmd = new UpdateVMScheduleCmd(); @@ -60,10 +64,18 @@ public void tearDown() throws Exception { */ @Test public void testSuccessfulExecution() { - VMScheduleResponse vmScheduleResponse = Mockito.mock(VMScheduleResponse.class); - Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenReturn(vmScheduleResponse); + ResourceScheduleResponse scheduleResponse = new ResourceScheduleResponse(); + scheduleResponse.setId("schedule-uuid"); + scheduleResponse.setResourceId("vm-uuid"); + Mockito.when(resourceScheduleManager.updateSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenReturn(scheduleResponse); updateVMScheduleCmd.execute(); - Assert.assertEquals(vmScheduleResponse, updateVMScheduleCmd.getResponseObject()); + VMScheduleResponse response = (VMScheduleResponse) updateVMScheduleCmd.getResponseObject(); + Assert.assertNotNull(response); + Assert.assertEquals("schedule-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "id")); + Assert.assertEquals("vm-uuid", org.springframework.test.util.ReflectionTestUtils.getField(response, "vmId")); } /** @@ -73,7 +85,10 @@ public void testSuccessfulExecution() { */ @Test(expected = InvalidParameterValueException.class) public void testInvalidParameterValueException() { - Mockito.when(vmScheduleManager.updateSchedule(updateVMScheduleCmd)).thenThrow(InvalidParameterValueException.class); + Mockito.when(resourceScheduleManager.updateSchedule( + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any(), + Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any() + )).thenThrow(new InvalidParameterValueException("Invalid schedule")); updateVMScheduleCmd.execute(); } @@ -84,11 +99,12 @@ public void testInvalidParameterValueException() { */ @Test public void testSuccessfulGetEntityOwnerId() { - VMSchedule vmSchedule = Mockito.mock(VMSchedule.class); + ResourceSchedule schedule = Mockito.mock(ResourceSchedule.class); VirtualMachine vm = Mockito.mock(VirtualMachine.class); - Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(vmSchedule); - Mockito.when(entityManager.findById(VirtualMachine.class, vmSchedule.getVmId())).thenReturn(vm); + Mockito.when(schedule.getResourceType()).thenReturn(ApiCommandResourceType.VirtualMachine); + Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(schedule); + Mockito.when(entityManager.findById(VirtualMachine.class, schedule.getResourceId())).thenReturn(vm); long ownerId = updateVMScheduleCmd.getEntityOwnerId(); Assert.assertEquals(vm.getAccountId(), ownerId); @@ -101,8 +117,7 @@ public void testSuccessfulGetEntityOwnerId() { */ @Test(expected = InvalidParameterValueException.class) public void testFailureGetEntityOwnerId() { - VMSchedule vmSchedule = Mockito.mock(VMSchedule.class); - Mockito.when(entityManager.findById(VMSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null); - long ownerId = updateVMScheduleCmd.getEntityOwnerId(); + Mockito.when(entityManager.findById(ResourceSchedule.class, updateVMScheduleCmd.getId())).thenReturn(null); + updateVMScheduleCmd.getEntityOwnerId(); } } diff --git a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmdTest.java b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmdTest.java index 2505c67e87db..18c03f8f4bb6 100644 --- a/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmdTest.java +++ b/api/src/test/java/org/apache/cloudstack/api/command/user/vpc/CreateVPCCmdTest.java @@ -25,7 +25,6 @@ import com.cloud.network.vpc.VpcService; import com.cloud.user.AccountService; import com.cloud.utils.db.EntityManager; -import junit.framework.TestCase; import org.apache.cloudstack.api.ResponseGenerator; import org.apache.cloudstack.api.ResponseObject; import org.apache.cloudstack.api.response.VpcResponse; @@ -39,7 +38,7 @@ import org.springframework.test.util.ReflectionTestUtils; @RunWith(MockitoJUnitRunner.class) -public class CreateVPCCmdTest extends TestCase { +public class CreateVPCCmdTest { @Mock public VpcService _vpcService; @@ -47,9 +46,13 @@ public class CreateVPCCmdTest extends TestCase { public EntityManager _entityMgr; @Mock public AccountService _accountService; - private ResponseGenerator responseGenerator; + @Mock + private ResponseGenerator _responseGenerator; + @Mock + private Object job; + @InjectMocks - CreateVPCCmd cmd = new CreateVPCCmd(); + CreateVPCCmd cmd; @Test public void testGetAccountName() { @@ -185,11 +188,9 @@ public void testExecute() throws ResourceUnavailableException, InsufficientCapac VpcResponse response = Mockito.mock(VpcResponse.class); ReflectionTestUtils.setField(cmd, "id", 1L); - responseGenerator = Mockito.mock(ResponseGenerator.class); Mockito.doNothing().when(_vpcService).startVpc(cmd); Mockito.when(_entityMgr.findById(Mockito.eq(Vpc.class), Mockito.any(Long.class))).thenReturn(vpc); - cmd._responseGenerator = responseGenerator; - Mockito.when(responseGenerator.createVpcResponse(ResponseObject.ResponseView.Restricted, vpc)).thenReturn(response); + Mockito.when(_responseGenerator.createVpcResponse(ResponseObject.ResponseView.Restricted, vpc)).thenReturn(response); cmd.execute(); Mockito.verify(_vpcService, Mockito.times(1)).startVpc(cmd); } 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/api/src/test/java/org/apache/cloudstack/api/response/AttachedIsoResponseTest.java b/api/src/test/java/org/apache/cloudstack/api/response/AttachedIsoResponseTest.java new file mode 100644 index 000000000000..09d4eb598ab0 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/response/AttachedIsoResponseTest.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.api.response; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public final class AttachedIsoResponseTest { + + @Test + public void testFullConstructorPopulatesAllFields() { + AttachedIsoResponse response = new AttachedIsoResponse("uuid-1", "alpine-iso", "Alpine boot", 3, true); + Assert.assertEquals("uuid-1", response.getId()); + Assert.assertEquals("alpine-iso", response.getName()); + Assert.assertEquals("Alpine boot", response.getDisplayText()); + Assert.assertEquals(Integer.valueOf(3), response.getDeviceSeq()); + Assert.assertTrue(response.getBootable()); + } + + @Test + public void testNoArgConstructorLeavesFieldsNull() { + AttachedIsoResponse response = new AttachedIsoResponse(); + Assert.assertNull(response.getId()); + Assert.assertNull(response.getName()); + Assert.assertNull(response.getDisplayText()); + Assert.assertNull(response.getDeviceSeq()); + Assert.assertNull(response.getBootable()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/api/response/LoginCmdResponseTest.java b/api/src/test/java/org/apache/cloudstack/api/response/LoginCmdResponseTest.java new file mode 100644 index 000000000000..7811138fffe1 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/api/response/LoginCmdResponseTest.java @@ -0,0 +1,87 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.api.response; + + +import org.junit.Assert; +import org.junit.Test; + +public class LoginCmdResponseTest { + + @Test + public void testAllGettersAndSetters() { + LoginCmdResponse response = new LoginCmdResponse(); + + response.setUsername("user1"); + response.setUserId("100"); + response.setDomainId("200"); + response.setTimeout(3600); + response.setAccount("account1"); + response.setFirstName("John"); + response.setLastName("Doe"); + response.setType("admin"); + response.setTimeZone("UTC"); + response.setTimeZoneOffset("+00:00"); + response.setRegistered("true"); + response.setSessionKey("session-key"); + response.set2FAenabled("true"); + response.set2FAverfied("false"); + response.setProviderFor2FA("totp"); + response.setIssuerFor2FA("cloudstack"); + response.setManagementServerId("ms-1"); + + Assert.assertEquals("user1", response.getUsername()); + Assert.assertEquals("100", response.getUserId()); + Assert.assertEquals("200", response.getDomainId()); + Assert.assertEquals(Integer.valueOf(3600), response.getTimeout()); + Assert.assertEquals("account1", response.getAccount()); + Assert.assertEquals("John", response.getFirstName()); + Assert.assertEquals("Doe", response.getLastName()); + Assert.assertEquals("admin", response.getType()); + Assert.assertEquals("UTC", response.getTimeZone()); + Assert.assertEquals("+00:00", response.getTimeZoneOffset()); + Assert.assertEquals("true", response.getRegistered()); + Assert.assertEquals("session-key", response.getSessionKey()); + Assert.assertEquals("true", response.is2FAenabled()); + Assert.assertEquals("false", response.is2FAverfied()); + Assert.assertEquals("totp", response.getProviderFor2FA()); + Assert.assertEquals("cloudstack", response.getIssuerFor2FA()); + Assert.assertEquals("ms-1", response.getManagementServerId()); + } + + @Test + public void testPasswordChangeRequired_True() { + LoginCmdResponse response = new LoginCmdResponse(); + response.setPasswordChangeRequired(true); + Assert.assertTrue(response.getPasswordChangeRequired()); + } + + @Test + public void testPasswordChangeRequired_False() { + LoginCmdResponse response = new LoginCmdResponse(); + response.setPasswordChangeRequired(false); + Assert.assertFalse(response.getPasswordChangeRequired()); + } + + @Test + public void testPasswordChangeRequired_Null() { + LoginCmdResponse response = new LoginCmdResponse(); + response.setPasswordChangeRequired(null); + Assert.assertNull("Boolean.parseBoolean(null) should return null", response.getPasswordChangeRequired()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/config/ApiServiceConfigurationTest.java b/api/src/test/java/org/apache/cloudstack/config/ApiServiceConfigurationTest.java new file mode 100644 index 000000000000..4e96af3ead41 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/config/ApiServiceConfigurationTest.java @@ -0,0 +1,95 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.config; + +import com.cloud.exception.InvalidParameterValueException; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class ApiServiceConfigurationTest { + + private static final String LOCALHOST = "http://localhost"; + + private static final String ENDPOINT_URL = "https://acs.clouds.com/client/api"; + + private static final String WHITE_SPACE = " "; + + private static final String BLANK_STRING = ""; + + private static final String NULL_STRING = null; + + private static final String LOCALHOST_IP = "127.0.0.1"; + + @Test(expected = InvalidParameterValueException.class) + public void validateEndpointUrlTestIfEndpointUrlContainLocalhostShouldThrowInvalidParameterValueException() { + try (MockedStatic apiServiceConfigurationMockedStatic = Mockito.mockStatic(ApiServiceConfiguration.class)) { + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::getApiServletPathValue).thenReturn(LOCALHOST); + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::validateEndpointUrl).thenCallRealMethod(); + ApiServiceConfiguration.validateEndpointUrl(); + } + } + + @Test + public void validateEndpointUrlTestIfEndpointUrlContainLocalhostShouldNotThrowInvalidParameterValueException() { + try (MockedStatic apiServiceConfigurationMockedStatic = Mockito.mockStatic(ApiServiceConfiguration.class)) { + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::getApiServletPathValue).thenReturn(ENDPOINT_URL); + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::validateEndpointUrl).thenCallRealMethod(); + ApiServiceConfiguration.validateEndpointUrl(); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void validateEndpointUrlTestIfEndpointUrlIsNullShouldThrowInvalidParameterValueException() { + try (MockedStatic apiServiceConfigurationMockedStatic = Mockito.mockStatic(ApiServiceConfiguration.class)) { + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::getApiServletPathValue).thenReturn(NULL_STRING); + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::validateEndpointUrl).thenCallRealMethod(); + ApiServiceConfiguration.validateEndpointUrl(); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void validateEndpointUrlTestIfEndpointUrlIsBlankShouldThrowInvalidParameterValueException() { + try (MockedStatic apiServiceConfigurationMockedStatic = Mockito.mockStatic(ApiServiceConfiguration.class)) { + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::getApiServletPathValue).thenReturn(BLANK_STRING); + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::validateEndpointUrl).thenCallRealMethod(); + ApiServiceConfiguration.validateEndpointUrl(); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void validateEndpointUrlTestIfEndpointUrlIsWhiteSpaceShouldThrowInvalidParameterValueException() { + try (MockedStatic apiServiceConfigurationMockedStatic = Mockito.mockStatic(ApiServiceConfiguration.class)) { + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::getApiServletPathValue).thenReturn(WHITE_SPACE); + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::validateEndpointUrl).thenCallRealMethod(); + ApiServiceConfiguration.validateEndpointUrl(); + } + } + + @Test(expected = InvalidParameterValueException.class) + public void validateEndpointUrlTestIfEndpointUrlContainLocalhostIpShouldThrowInvalidParameterValueException() { + try (MockedStatic apiServiceConfigurationMockedStatic = Mockito.mockStatic(ApiServiceConfiguration.class)) { + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::getApiServletPathValue).thenReturn(LOCALHOST_IP); + apiServiceConfigurationMockedStatic.when(ApiServiceConfiguration::validateEndpointUrl).thenCallRealMethod(); + ApiServiceConfiguration.validateEndpointUrl(); + } + } +} diff --git a/api/src/test/java/org/apache/cloudstack/dns/DnsRecordTest.java b/api/src/test/java/org/apache/cloudstack/dns/DnsRecordTest.java new file mode 100644 index 000000000000..bd993380e068 --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/dns/DnsRecordTest.java @@ -0,0 +1,105 @@ +// 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.dns; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; + +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import com.cloud.utils.exception.CloudRuntimeException; + +public class DnsRecordTest { + + @Test + public void testDefaultConstructor() { + DnsRecord record = new DnsRecord(); + assertNull(record.getName()); + assertNull(record.getType()); + assertNull(record.getContents()); + assertEquals(0, record.getTtl()); + } + + @Test + public void testParameterizedConstructor() { + List contents = Arrays.asList("192.168.1.1"); + DnsRecord record = new DnsRecord("www", DnsRecord.RecordType.A, contents, 3600); + + assertEquals("www", record.getName()); + assertEquals(DnsRecord.RecordType.A, record.getType()); + assertEquals(contents, record.getContents()); + assertEquals(3600, record.getTtl()); + } + + @Test + public void testSettersAndGetters() { + DnsRecord record = new DnsRecord(); + List contents = Arrays.asList("10.0.0.1", "10.0.0.2"); + + record.setName("mail"); + record.setType(DnsRecord.RecordType.AAAA); + record.setContents(contents); + record.setTtl(7200); + + assertEquals("mail", record.getName()); + assertEquals(DnsRecord.RecordType.AAAA, record.getType()); + assertEquals(contents, record.getContents()); + assertEquals(7200, record.getTtl()); + } + + // RecordType.fromString tests + + @Test + public void testFromStringValid() { + assertEquals(DnsRecord.RecordType.A, DnsRecord.RecordType.fromString("A")); + assertEquals(DnsRecord.RecordType.AAAA, DnsRecord.RecordType.fromString("AAAA")); + assertEquals(DnsRecord.RecordType.CNAME, DnsRecord.RecordType.fromString("CNAME")); + assertEquals(DnsRecord.RecordType.MX, DnsRecord.RecordType.fromString("MX")); + assertEquals(DnsRecord.RecordType.TXT, DnsRecord.RecordType.fromString("TXT")); + assertEquals(DnsRecord.RecordType.SRV, DnsRecord.RecordType.fromString("SRV")); + assertEquals(DnsRecord.RecordType.PTR, DnsRecord.RecordType.fromString("PTR")); + assertEquals(DnsRecord.RecordType.NS, DnsRecord.RecordType.fromString("NS")); + } + + @Test + public void testFromStringCaseInsensitive() { + assertEquals(DnsRecord.RecordType.A, DnsRecord.RecordType.fromString("a")); + assertEquals(DnsRecord.RecordType.CNAME, DnsRecord.RecordType.fromString("cname")); + assertEquals(DnsRecord.RecordType.MX, DnsRecord.RecordType.fromString("mx")); + } + + @Test + public void testFromStringNull() { + assertNull(DnsRecord.RecordType.fromString(null)); + } + + @Test(expected = CloudRuntimeException.class) + public void testFromStringInvalid() { + DnsRecord.RecordType.fromString("INVALID"); + } + + @Test + public void testRecordTypeValues() { + DnsRecord.RecordType[] values = DnsRecord.RecordType.values(); + assertNotNull(values); + assertEquals(8, values.length); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/extension/CustomActionResultResponseTest.java b/api/src/test/java/org/apache/cloudstack/extension/CustomActionResultResponseTest.java new file mode 100644 index 000000000000..9b7346f332cb --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/extension/CustomActionResultResponseTest.java @@ -0,0 +1,92 @@ +// 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.extension; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Map; + +import org.junit.Before; +import org.junit.Test; + +public class CustomActionResultResponseTest { + + private CustomActionResultResponse response; + + @Before + public void setUp() { + response = new CustomActionResultResponse(); + } + + @Test + public void getResultReturnsNullByDefault() { + assertNull(response.getResult()); + } + + @Test + public void getResultReturnsSetValue() { + Map result = Map.of("message", "OK", "details", "All good"); + response.setResult(result); + assertEquals(result, response.getResult()); + assertEquals("OK", response.getResult().get("message")); + } + + @Test + public void isSuccessReturnsFalseWhenSuccessIsNull() { + // success is null by default + assertFalse(response.isSuccess()); + } + + @Test + public void isSuccessReturnsFalseWhenSuccessIsFalse() { + response.setSuccess(false); + assertFalse(response.isSuccess()); + } + + @Test + public void isSuccessReturnsTrueWhenSuccessIsTrue() { + response.setSuccess(true); + assertTrue(response.isSuccess()); + } + + @Test + public void getSuccessReturnsNullByDefault() { + assertNull(response.getSuccess()); + } + + @Test + public void getSuccessReturnsTrueAfterSetSuccessTrue() { + response.setSuccess(true); + assertTrue(response.getSuccess()); + } + + @Test + public void getSuccessReturnsFalseAfterSetSuccessFalse() { + response.setSuccess(false); + assertFalse(response.getSuccess()); + } + + @Test + public void setAndGetResultWithNullResult() { + response.setResult(null); + assertNull(response.getResult()); + } +} diff --git a/api/src/test/java/org/apache/cloudstack/extension/ExtensionCustomActionTest.java b/api/src/test/java/org/apache/cloudstack/extension/ExtensionCustomActionTest.java new file mode 100644 index 000000000000..a6bd49a2d8be --- /dev/null +++ b/api/src/test/java/org/apache/cloudstack/extension/ExtensionCustomActionTest.java @@ -0,0 +1,490 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//with the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.extension; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.api.ApiConstants; +import org.junit.Test; + +import com.cloud.exception.InvalidParameterValueException; + +public class ExtensionCustomActionTest { + + @Test + public void testResourceType() { + ExtensionCustomAction.ResourceType vmType = ExtensionCustomAction.ResourceType.VirtualMachine; + assertEquals(com.cloud.vm.VirtualMachine.class, vmType.getAssociatedClass()); + } + + @Test + public void testNetworkResourceType() { + ExtensionCustomAction.ResourceType networkType = ExtensionCustomAction.ResourceType.Network; + assertEquals(com.cloud.network.Network.class, networkType.getAssociatedClass()); + } + + @Test + public void testParameterTypeSupportsOptions() { + assertTrue(ExtensionCustomAction.Parameter.Type.STRING.canSupportsOptions()); + assertTrue(ExtensionCustomAction.Parameter.Type.NUMBER.canSupportsOptions()); + assertFalse(ExtensionCustomAction.Parameter.Type.BOOLEAN.canSupportsOptions()); + assertFalse(ExtensionCustomAction.Parameter.Type.DATE.canSupportsOptions()); + } + + @Test + public void testValidationFormatBaseType() { + assertEquals(ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.UUID.getBaseType()); + assertEquals(ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.EMAIL.getBaseType()); + assertEquals(ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.PASSWORD.getBaseType()); + assertEquals(ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.URL.getBaseType()); + assertEquals(ExtensionCustomAction.Parameter.Type.NUMBER, + ExtensionCustomAction.Parameter.ValidationFormat.DECIMAL.getBaseType()); + assertNull(ExtensionCustomAction.Parameter.ValidationFormat.NONE.getBaseType()); + } + + @Test + public void testParameterFromMapValid() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "STRING"); + map.put(ApiConstants.VALIDATION_FORMAT, "EMAIL"); + map.put(ApiConstants.REQUIRED, "true"); + map.put(ApiConstants.VALUE_OPTIONS, "test@example.com,another@test.com"); + + ExtensionCustomAction.Parameter param = ExtensionCustomAction.Parameter.fromMap(map); + + assertEquals("testParam", param.getName()); + assertEquals(ExtensionCustomAction.Parameter.Type.STRING, param.getType()); + assertEquals(ExtensionCustomAction.Parameter.ValidationFormat.EMAIL, param.getValidationFormat()); + assertTrue(param.isRequired()); + assertEquals(2, param.getValueOptions().size()); + assertTrue(param.getValueOptions().contains("test@example.com")); + assertTrue(param.getValueOptions().contains("another@test.com")); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapEmptyName() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, ""); + map.put(ApiConstants.TYPE, "STRING"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapNoType() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapInvalidType() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "INVALID_TYPE"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapInvalidValidationFormat() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "STRING"); + map.put(ApiConstants.VALIDATION_FORMAT, "INVALID_FORMAT"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapMismatchedTypeAndFormat() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "STRING"); + map.put(ApiConstants.VALIDATION_FORMAT, "DECIMAL"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test + public void testParameterFromMapWithNumberOptions() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "NUMBER"); + map.put(ApiConstants.VALIDATION_FORMAT, "DECIMAL"); + map.put(ApiConstants.VALUE_OPTIONS, "1.5,2.7,3.0"); + + ExtensionCustomAction.Parameter param = ExtensionCustomAction.Parameter.fromMap(map); + + assertEquals(ExtensionCustomAction.Parameter.Type.NUMBER, param.getType()); + assertEquals(ExtensionCustomAction.Parameter.ValidationFormat.DECIMAL, param.getValidationFormat()); + assertEquals(3, param.getValueOptions().size()); + assertTrue(param.getValueOptions().contains(1.5f)); + assertTrue(param.getValueOptions().contains(2.7f)); + assertTrue(param.getValueOptions().contains(3.0f)); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapInvalidNumberOptions() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "NUMBER"); + map.put(ApiConstants.VALUE_OPTIONS, "1.5,invalid,3.0"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test(expected = InvalidParameterValueException.class) + public void testParameterFromMapInvalidEmailOptions() { + Map map = new HashMap<>(); + map.put(ApiConstants.NAME, "testParam"); + map.put(ApiConstants.TYPE, "STRING"); + map.put(ApiConstants.VALIDATION_FORMAT, "EMAIL"); + map.put(ApiConstants.VALUE_OPTIONS, "valid@email.com,invalid-email"); + + ExtensionCustomAction.Parameter.fromMap(map); + } + + @Test + public void testValidatedValueString() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.EMAIL, + null, + false + ); + + Object result = param.validatedValue("test@example.com"); + assertEquals("test@example.com", result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueInvalidEmail() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.EMAIL, + null, + false + ); + + param.validatedValue("invalid-email"); + } + + @Test + public void testValidatedValueUUID() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.UUID, + null, + false + ); + + String validUUID = "550e8400-e29b-41d4-a716-446655440000"; + Object result = param.validatedValue(validUUID); + assertEquals(validUUID, result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueInvalidUUID() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.UUID, + null, + false + ); + + param.validatedValue("invalid-uuid"); + } + + @Test + public void testValidatedValueURL() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.URL, + null, + false + ); + + Object result = param.validatedValue("https://example.com"); + assertEquals("https://example.com", result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueInvalidURL() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.URL, + null, + false + ); + + param.validatedValue("not-a-url"); + } + + @Test + public void testValidatedValuePassword() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.PASSWORD, + null, + false + ); + + Object result = param.validatedValue("mypassword"); + assertEquals("mypassword", result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueEmptyPassword() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.PASSWORD, + null, + false + ); + + param.validatedValue(" "); + } + + @Test + public void testValidatedValueNumber() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.NUMBER, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + null, + false + ); + + Object result = param.validatedValue("42"); + assertEquals(42, result); + } + + @Test + public void testValidatedValueDecimal() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.NUMBER, + ExtensionCustomAction.Parameter.ValidationFormat.DECIMAL, + null, + false + ); + + Object result = param.validatedValue("3.14"); + assertEquals(3.14f, result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueInvalidNumber() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.NUMBER, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + null, + false + ); + + param.validatedValue("not-a-number"); + } + + @Test + public void testValidatedValueBoolean() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.BOOLEAN, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + null, + false + ); + + Object result = param.validatedValue("true"); + assertEquals(true, result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueInvalidBoolean() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.BOOLEAN, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + null, + false + ); + + Object result = param.validatedValue("maybe"); + } + + @Test + public void testValidatedValueWithOptions() { + List options = Arrays.asList("option1", "option2", "option3"); + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + options, + false + ); + + Object result = param.validatedValue("option2"); + assertEquals("option2", result); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueNotInOptions() { + List options = Arrays.asList("option1", "option2", "option3"); + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + options, + false + ); + + param.validatedValue("option4"); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidatedValueEmpty() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, + null, + false + ); + + param.validatedValue(""); + } + + @Test + public void testValidateParameterValues() { + List paramDefs = Arrays.asList( + new ExtensionCustomAction.Parameter("required1", ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, null, true), + new ExtensionCustomAction.Parameter("required2", ExtensionCustomAction.Parameter.Type.NUMBER, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, null, true), + new ExtensionCustomAction.Parameter("optional", ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, null, false) + ); + + Map suppliedValues = new HashMap<>(); + suppliedValues.put("required1", "value1"); + suppliedValues.put("required2", "42"); + suppliedValues.put("optional", "optionalValue"); + + Map result = ExtensionCustomAction.Parameter.validateParameterValues(paramDefs, suppliedValues); + + assertEquals("value1", result.get("required1")); + assertEquals(42, result.get("required2")); + assertEquals("optionalValue", result.get("optional")); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateParameterValuesMissingRequired() { + List paramDefs = Arrays.asList( + new ExtensionCustomAction.Parameter("required1", ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, null, true) + ); + + Map suppliedValues = new HashMap<>(); + + ExtensionCustomAction.Parameter.validateParameterValues(paramDefs, suppliedValues); + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateParameterValuesEmptyRequired() { + List paramDefs = Arrays.asList( + new ExtensionCustomAction.Parameter("required1", ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, null, true) + ); + + Map suppliedValues = new HashMap<>(); + suppliedValues.put("required1", " "); + + ExtensionCustomAction.Parameter.validateParameterValues(paramDefs, suppliedValues); + } + + @Test + public void testValidateParameterValuesNullSupplied() { + List paramDefs = Arrays.asList( + new ExtensionCustomAction.Parameter("optional", ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.NONE, null, false) + ); + + Map result = ExtensionCustomAction.Parameter.validateParameterValues(paramDefs, null); + assertTrue(result.isEmpty()); + } + + @Test + public void testJsonSerializationDeserialization() { + List originalParams = Arrays.asList( + new ExtensionCustomAction.Parameter("param1", ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.EMAIL, Arrays.asList("test@example.com"), true), + new ExtensionCustomAction.Parameter("param2", ExtensionCustomAction.Parameter.Type.NUMBER, + ExtensionCustomAction.Parameter.ValidationFormat.DECIMAL, Arrays.asList(1.5f, 2.7f), false) + ); + + String json = ExtensionCustomAction.Parameter.toJsonFromList(originalParams); + List deserializedParams = ExtensionCustomAction.Parameter.toListFromJson(json); + + assertEquals(originalParams.size(), deserializedParams.size()); + assertEquals(originalParams.get(0).getName(), deserializedParams.get(0).getName()); + assertEquals(originalParams.get(0).getType(), deserializedParams.get(0).getType()); + assertEquals(originalParams.get(0).getValidationFormat(), deserializedParams.get(0).getValidationFormat()); + assertEquals(originalParams.get(0).isRequired(), deserializedParams.get(0).isRequired()); + assertEquals(originalParams.get(0).getValueOptions(), deserializedParams.get(0).getValueOptions()); + } + + @Test + public void testToString() { + ExtensionCustomAction.Parameter param = new ExtensionCustomAction.Parameter( + "testParam", + ExtensionCustomAction.Parameter.Type.STRING, + ExtensionCustomAction.Parameter.ValidationFormat.EMAIL, + null, + true + ); + + String result = param.toString(); + assertTrue(result.contains("testParam")); + assertTrue(result.contains("STRING")); + assertTrue(result.contains("true")); + } +} diff --git a/build/replace.properties b/build/replace.properties index ce38727b80a7..8c3812eb7d29 100644 --- a/build/replace.properties +++ b/build/replace.properties @@ -28,3 +28,4 @@ MSMNTDIR=/mnt COMPONENTS-SPEC=components.xml REMOTEHOST=localhost COMMONLIBDIR=client/target/common/ +EXTENSIONSDEPLOYMENTMODE=developer diff --git a/client/bindir/cloud-setup-management.in b/client/bindir/cloud-setup-management.in index 84c87ae2e442..b4fe76cc8d8a 100755 --- a/client/bindir/cloud-setup-management.in +++ b/client/bindir/cloud-setup-management.in @@ -36,6 +36,106 @@ from cloudutils.cloudException import CloudRuntimeException, CloudInternalExcept from cloudutils.globalEnv import globalEnv from cloudutils.serviceConfigServer import cloudManagementConfig from optparse import OptionParser +import urllib.request +import configparser +import hashlib + +SYSTEMVM_TEMPLATES_PATH = "/usr/share/cloudstack-management/templates/systemvm" +SYSTEMVM_TEMPLATES_METADATA_FILE = SYSTEMVM_TEMPLATES_PATH + "/metadata.ini" + +def verify_sha512_checksum(file_path, expected_checksum): + sha512 = hashlib.sha512() + try: + with open(file_path, "rb") as f: + for chunk in iter(lambda: f.read(8192), b""): + sha512.update(chunk) + return sha512.hexdigest().lower() == expected_checksum.lower() + except Exception as e: + print(f"Failed to verify checksum for {file_path}: {e}") + return False + +def download_file(url, dest_path, chunk_size=8 * 1024 * 1024): + """ + Downloads a file from the given URL to the specified destination path in chunks. + """ + try: + with urllib.request.urlopen(url) as response: + total_size = response.length if response.length else None + downloaded = 0 + try: + with open(dest_path, 'wb') as out_file: + while True: + chunk = response.read(chunk_size) + if not chunk: + break + out_file.write(chunk) + downloaded += len(chunk) + if total_size: + print(f"Downloaded {downloaded / (1024 * 1024):.2f}MB of {total_size / (1024 * 1024):.2f}MB", end='\r') + except PermissionError as pe: + print(f"Permission denied: {dest_path}") + raise + print(f"\nDownloaded file from {url} to {dest_path}") + except Exception as e: + print(f"Failed to download file: {e}") + raise + +def download_template_if_needed(template, url, filename, checksum): + dest_path = os.path.join(SYSTEMVM_TEMPLATES_PATH, filename) + if os.path.exists(dest_path): + if checksum and verify_sha512_checksum(dest_path, checksum): + print(f"{template} System VM template already exists at {dest_path} with valid checksum, skipping download.") + return + else: + print(f"{template} System VM template at {dest_path} has invalid or missing checksum, re-downloading...") + else: + print(f"Downloading {template} System VM template from {url} to {dest_path}...") + try: + download_file(url, dest_path) + #After download, verify checksum if provided + if checksum: + if verify_sha512_checksum(dest_path, checksum): + print(f"{template} System VM template downloaded and verified successfully.") + else: + print(f"ERROR: Checksum verification failed for {template} System VM template after download.") + except Exception as e: + print(f"ERROR: Failed to download {template} System VM template: {e}") + +def collect_template_metadata(selected_templates, options): + template_metadata_list = [] + if not os.path.exists(SYSTEMVM_TEMPLATES_METADATA_FILE): + print(f"ERROR: System VM templates metadata file not found at {SYSTEMVM_TEMPLATES_METADATA_FILE}, cannot download templates.") + sys.exit(1) + config = configparser.ConfigParser() + config.read(SYSTEMVM_TEMPLATES_METADATA_FILE) + template_repo_url = None + if options.systemvm_templates_repository: + if "default" in config and "downloadrepository" in config["default"]: + template_repo_url = config["default"]["downloadrepository"].strip() + if not template_repo_url: + print("ERROR: downloadrepository value is empty in metadata file, cannot use --systemvm-template-repository option.") + sys.exit(1) + for template in selected_templates: + if template in config: + url = config[template].get("downloadurl") + filename = config[template].get("filename") + checksum = config[template].get("checksum") + if url and filename: + if template_repo_url: + url = url.replace(template_repo_url, options.systemvm_templates_repository) + template_metadata_list.append({ + "template": template, + "url": url, + "filename": filename, + "checksum": checksum + }) + else: + print(f"ERROR: URL or filename not found for {template} System VM template in metadata.") + sys.exit(1) + else: + print(f"ERROR: No metadata found for {template} System VM template.") + sys.exit(1) + return template_metadata_list if __name__ == '__main__': initLoging("@MSLOGDIR@/setupManagement.log") @@ -45,6 +145,16 @@ if __name__ == '__main__': parser.add_option("--https", action="store_true", dest="https", help="Enable HTTPs connection of management server") parser.add_option("--tomcat7", action="store_true", dest="tomcat7", help="Depreciated option, don't use it") parser.add_option("--no-start", action="store_true", dest="nostart", help="Do not start management server after successful configuration") + parser.add_option( + "--systemvm-templates", + dest="systemvm_templates", + help="Specify System VM templates to download: all, kvm-aarch64, kvm-x86_64, xenserver, vmware or comma-separated list of hypervisor combinations (e.g., kvm-x86_64,xenserver). Default is kvm-x86_64.", + ) + parser.add_option( + "--systemvm-templates-repository", + dest="systemvm_templates_repository", + help="Specify the URL to download System VM templates from." + ) (options, args) = parser.parse_args() if options.https: glbEnv.svrMode = "HttpsServer" @@ -53,6 +163,34 @@ if __name__ == '__main__': if options.nostart: glbEnv.noStart = True + available_templates = ["kvm-aarch64", "kvm-x86_64", "xenserver", "vmware"] + templates_arg = options.systemvm_templates + + selected_templates = ["kvm-x86_64"] + if templates_arg: + templates_list = [t.strip().lower() for t in templates_arg.split(",")] + if "all" in templates_list: + if len(templates_list) > 1: + print("WARNING: 'all' specified for System VM templates, ignoring other specified templates.") + selected_templates = available_templates + else: + invalid_templates = [] + for t in templates_list: + if t in available_templates: + if t not in selected_templates: + selected_templates.append(t) + else: + if t not in invalid_templates: + invalid_templates.append(t) + if invalid_templates: + print(f"ERROR: Invalid System VM template names provided: {', '.join(invalid_templates)}") + sys.exit(1) + print(f"Selected systemvm templates to download: {', '.join(selected_templates) if selected_templates else 'None'}") + + template_metadata_list = [] + if selected_templates: + template_metadata_list = collect_template_metadata(selected_templates, options) + glbEnv.mode = "Server" print("Starting to configure CloudStack Management Server:") @@ -74,3 +212,6 @@ if __name__ == '__main__': syscfg.restore() except: pass + + for meta in template_metadata_list: + download_template_if_needed(meta["template"], meta["url"], meta["filename"], meta["checksum"]) diff --git a/client/bindir/cloud-update-xenserver-licenses.in b/client/bindir/cloud-update-xenserver-licenses.in index 2be3a083f973..fca48f9eafbb 100755 --- a/client/bindir/cloud-update-xenserver-licenses.in +++ b/client/bindir/cloud-update-xenserver-licenses.in @@ -34,16 +34,19 @@ from threading import Thread # ---- 2) waf configured PYTHONDIR # ---- 3) System Python path for pythonpath in ( - "@PYTHONDIR@", - os.path.join(os.path.dirname(__file__),os.path.pardir,os.path.pardir,"python","lib"), - ): - if os.path.isdir(pythonpath): sys.path.insert(0,pythonpath) + "@PYTHONDIR@", + os.path.join( + os.path.dirname(__file__), os.path.pardir, os.path.pardir, "python", "lib" + ), +): + if os.path.isdir(pythonpath): + sys.path.insert(0, pythonpath) # ---- End snippet of code ---- from cloud_utils import check_call, CalledProcessError, read_properties cfg = "@MSCONF@/db.properties" -#---------------------- option parsing and command line checks ------------------------ +# ---------------------- option parsing and command line checks ------------------------ usage = """%prog <-a | host names / IP addresses...> @@ -51,132 +54,171 @@ usage = """%prog <-a | host names / IP addresses...> This command deploys the license file specified in the command line into a specific XenServer host or all XenServer hosts known to the management server.""" parser = OptionParser(usage=usage) -parser.add_option("-a", "--all", action="store_true", dest="all", default=False, - help="deploy to all known hosts rather that a single host") - -#------------------ functions -------------------- - -def e(msg): parser.error(msg) - -def getknownhosts(host,username,password): - conn = mysql.connector.connect(host=host, user=username, password=password) - cur = conn.cursor() - cur.execute("SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'username' and setup = 1") - usernames = dict(cur.fetchall()) - cur.execute("SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'password' and setup = 1") - passwords = dict(cur.fetchall()) - creds = dict( [ [x,(usernames[x],passwords[x])] for x in list(usernames.keys()) ] ) - cur.close() - conn.close() - return creds - -def splitlast(string,splitter): - splitted = string.split(splitter) - first,last = splitter.join(splitted[:-1]),splitted[-1] - return first,last +parser.add_option( + "-a", + "--all", + action="store_true", + dest="all", + default=False, + help="deploy to all known hosts rather that a single host", +) + +# ------------------ functions -------------------- + + +def e(msg): + parser.error(msg) + + +def getknownhosts(host, username, password): + conn = mysql.connector.connect(host=host, user=username, password=password) + cur = conn.cursor() + cur.execute( + "SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'username' and setup = 1" + ) + usernames = dict(cur.fetchall()) + cur.execute( + "SELECT h.private_ip_address,d.value FROM cloud.host h inner join cloud.host_details d on (h.id = d.host_id) where d.name = 'password' and setup = 1" + ) + passwords = dict(cur.fetchall()) + creds = dict([[x, (usernames[x], passwords[x])] for x in list(usernames.keys())]) + cur.close() + conn.close() + return creds + + +def splitlast(string, splitter): + splitted = string.split(splitter) + first, last = splitter.join(splitted[:-1]), splitted[-1] + return first, last + def parseuserpwfromhosts(hosts): - creds = {} - for host in hosts: - user = "root" - password = "" - if "@" in host: - user,host = splitlast(host,"@") - if ":" in user: - user,password = splitlast(user,":") - creds[host] = (user,password) - return creds + creds = {} + for host in hosts: + user = "root" + password = "" + if "@" in host: + user, host = splitlast(host, "@") + if ":" in user: + user, password = splitlast(user, ":") + creds[host] = (user, password) + return creds + class XenServerConfigurator(Thread): - def __init__(self,host,user,password,keyfiledata): - Thread.__init__(self) - self.host = host - self.user = user - self.password = password - self.keyfiledata = keyfiledata - self.retval = None # means all's good - self.stdout = "" - self.stderr = "" - self.state = 'initialized' - - def run(self): - try: - self.state = 'running' - c = paramiko.SSHClient() - c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - c.connect(self.host,username=self.user,password=self.password) - sftp = c.open_sftp() - sftp.chdir("/tmp") - f = sftp.open("xen-license","w") - f.write(self.keyfiledata) - f.close() - sftp.close() - stdin,stdout,stderr = c.exec_command("xe host-license-add license-file=/tmp/xen-license") - c.exec_command("false") - self.stdout = stdout.read(-1) - self.stderr = stderr.read(-1) - self.retval = stdin.channel.recv_exit_status() - c.close() - if self.retval != 0: self.state = 'failed' - else: self.state = 'finished' - - except Exception as e: - self.state = 'failed' - self.retval = e - #raise - - def __str__(self): - if self.state == 'failed': - return "<%s XenServerConfigurator on %s@%s: %s>"%(self.state,self.user,self.host,str(self.retval)) - else: - return "<%s XenServerConfigurator on %s@%s>"%(self.state,self.user,self.host) - -#------------- actual code -------------------- + def __init__(self, host, user, password, keyfiledata): + Thread.__init__(self) + self.host = host + self.user = user + self.password = password + self.keyfiledata = keyfiledata + self.retval = None # means all's good + self.stdout = "" + self.stderr = "" + self.state = "initialized" + + def run(self): + try: + self.state = "running" + c = paramiko.SSHClient() + c.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + c.connect(self.host, username=self.user, password=self.password) + sftp = c.open_sftp() + sftp.chdir("/tmp") + f = sftp.open("xen-license", "w") + f.write(self.keyfiledata) + f.close() + sftp.close() + stdin, stdout, stderr = c.exec_command( + "xe host-license-add license-file=/tmp/xen-license" + ) + c.exec_command("false") + self.stdout = stdout.read(-1) + self.stderr = stderr.read(-1) + self.retval = stdin.channel.recv_exit_status() + c.close() + if self.retval != 0: + self.state = "failed" + else: + self.state = "finished" + + except Exception as e: + self.state = "failed" + self.retval = e + # raise + + def __str__(self): + if self.state == "failed": + return "<%s XenServerConfigurator on %s@%s: %s>" % ( + self.state, + self.user, + self.host, + str(self.retval), + ) + else: + return "<%s XenServerConfigurator on %s@%s>" % ( + self.state, + self.user, + self.host, + ) + + +# ------------- actual code -------------------- (options, args) = parser.parse_args() try: - licensefile,args = args[0],args[1:] -except IndexError: e("The first argument must be the license file to use") + licensefile, args = args[0], args[1:] +except IndexError: + e("The first argument must be the license file to use") if options.all: - if len(args) != 0: e("IP addresses cannot be specified if -a is specified") - config = read_properties(cfg) - creds = getknownhosts(config["db.cloud.host"],config["db.cloud.username"],config["db.cloud.password"]) - hosts = list(creds.keys()) + if len(args) != 0: + e("IP addresses cannot be specified if -a is specified") + config = read_properties(cfg) + creds = getknownhosts( + config["db.cloud.host"], + config["db.cloud.username"], + config["db.cloud.password"], + ) + hosts = list(creds.keys()) else: - if not args: e("You must specify at least one IP address, or -a") - hosts = args - creds = parseuserpwfromhosts(hosts) + if not args: + e("You must specify at least one IP address, or -a") + hosts = args + creds = parseuserpwfromhosts(hosts) try: - keyfiledata = file(licensefile).read(-1) + keyfiledata = file(licensefile).read(-1) except OSError as e: - sys.stderr.write("The file %s cannot be opened"%licensefile) - sys.exit(1) + sys.stderr.write("The file %s cannot be opened" % licensefile) + sys.exit(1) configurators = [] -for host,(user,password) in list(creds.items()): - configurators.append ( XenServerConfigurator(host,user,password,keyfiledata ) ) +for host, (user, password) in list(creds.items()): + configurators.append(XenServerConfigurator(host, user, password, keyfiledata)) -for c in configurators: c.start() +for c in configurators: + c.start() for c in configurators: - print(c.host + "...", end=' ') - c.join() - if c.state == 'failed': - if c.retval: - msg = "failed with return code %s: %s%s"%(c.retval,c.stdout,c.stderr) - msg = msg.strip() - print(msg) - else: print("failed: %s"%c.retval) - else: - print("done") - -successes = len( [ a for a in configurators if not a.state == 'failed' ] ) -failures = len( [ a for a in configurators if a.state == 'failed' ] ) - -print("%3s successes"%successes) -print("%3s failures"%failures) + print(c.host + "...", end=" ") + c.join() + if c.state == "failed": + if c.retval: + msg = "failed with return code %s: %s%s" % (c.retval, c.stdout, c.stderr) + msg = msg.strip() + print(msg) + else: + print("failed: %s" % c.retval) + else: + print("done") + +successes = len([a for a in configurators if not a.state == "failed"]) +failures = len([a for a in configurators if a.state == "failed"]) + +print("%3s successes" % successes) +print("%3s failures" % failures) diff --git a/client/conf/server.properties.in b/client/conf/server.properties.in index 0a6078048d36..7c5e3f925b08 100644 --- a/client/conf/server.properties.in +++ b/client/conf/server.properties.in @@ -55,3 +55,15 @@ webapp.dir=/usr/share/cloudstack-management/webapp # The path to access log file access.log=/var/log/cloudstack/management/access.log + +# The deployment mode for the extensions +extensions.deployment.mode=@EXTENSIONSDEPLOYMENTMODE@ + +# Thread pool configuration +#threads.min=10 +#threads.max=500 + +# The URL prefix for the system VM templates repository. When downloading system VM templates, the server replaces the +# `downloadrepository` key value from the metadata file in template URLs. If not specified, the original template URL +# will be used for download. +# system.vm.templates.download.repository=http://download.cloudstack.org/systemvm/ diff --git a/client/pom.xml b/client/pom.xml index 2672ed811271..968f735af600 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT @@ -121,6 +121,11 @@ cloud-plugin-storage-volume-adaptive ${project.version} + + org.apache.cloudstack + cloud-plugin-storage-volume-ontap + ${project.version} + org.apache.cloudstack cloud-plugin-storage-volume-solidfire @@ -251,6 +256,16 @@ cloud-plugin-metrics ${project.version} + + org.apache.cloudstack + cloud-plugin-kms-database + ${project.version} + + + org.apache.cloudstack + cloud-plugin-kms-pkcs11 + ${project.version} + org.apache.cloudstack cloud-plugin-network-nvp @@ -321,16 +336,6 @@ cloud-plugin-hypervisor-ucs ${project.version} - - org.apache.cloudstack - cloud-plugin-hypervisor-ovm - ${project.version} - - - org.apache.cloudstack - cloud-plugin-hypervisor-ovm3 - ${project.version} - org.apache.cloudstack cloud-plugin-hypervisor-kvm @@ -347,6 +352,11 @@ cloud-plugin-hypervisor-hyperv ${project.version} + + org.apache.cloudstack + cloud-plugin-hypervisor-external + ${project.version} + org.apache.cloudstack cloud-plugin-storage-allocator-random @@ -377,11 +387,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 @@ -617,6 +622,16 @@ cloud-plugin-backup-nas ${project.version} + + org.apache.cloudstack + cloud-plugin-integrations-veeam-control-service + ${project.version} + + + org.apache.cloudstack + cloud-plugin-backup-kvm-backup-on-secondary-storage + ${project.version} + org.apache.cloudstack cloud-plugin-integrations-kubernetes-service @@ -624,7 +639,7 @@ org.apache.cloudstack - cloud-plugin-shutdown + cloud-plugin-maintenance ${project.version} @@ -642,6 +657,11 @@ cloud-plugin-storage-object-ceph ${project.version} + + org.apache.cloudstack + cloud-plugin-storage-object-cloudian + ${project.version} + org.apache.cloudstack cloud-plugin-storage-object-simulator @@ -662,20 +682,32 @@ cloud-utils ${project.version} + + org.apache.cloudstack + cloud-plugin-dns-powerdns + ${project.version} + + - ru.concerteza.buildnumber - maven-jgit-buildnumber-plugin - 1.2.6 + org.codehaus.mojo + buildnumber-maven-plugin + 3.2.0 git-buildnumber - extract-buildnumber + create prepare-package + + false + false + true + unknown + @@ -688,11 +720,11 @@ org.apache.cloudstack.ServerDaemon - ${git.branch} - ${git.tag} - ${git.revision} - ${git.revision} - ${git.branch} + ${scmBranch} + ${project.version} + ${buildNumber} + ${buildNumber} + ${scmBranch} @@ -710,17 +742,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} @@ -900,13 +932,13 @@ org.bouncycastle - bcprov-jdk15on + bcprov-jdk18on false ${project.build.directory}/lib org.bouncycastle - bcpkix-jdk15on + bcpkix-jdk18on false ${project.build.directory}/lib @@ -930,7 +962,7 @@ org.bouncycastle - bctls-jdk15on + bctls-jdk18on false ${project.build.directory}/lib @@ -965,9 +997,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 @@ -1097,6 +1129,11 @@ cloud-plugin-network-nsx ${project.version} + + org.apache.cloudstack + cloud-plugin-network-netris + ${project.version} + org.apache.cloudstack cloud-plugin-network-tungsten diff --git a/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java b/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java new file mode 100644 index 000000000000..249451120f1c --- /dev/null +++ b/client/src/main/java/org/apache/cloudstack/ACSRequestLog.java @@ -0,0 +1,85 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack; + +import com.cloud.api.ApiServlet; +import com.cloud.utils.StringUtils; +import org.eclipse.jetty.server.NCSARequestLog; +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Response; +import org.eclipse.jetty.util.DateCache; +import org.eclipse.jetty.util.component.LifeCycle; + +import java.net.InetAddress; +import java.util.Locale; +import java.util.TimeZone; + +import static org.apache.commons.configuration.DataConfiguration.DEFAULT_DATE_FORMAT; + +public class ACSRequestLog extends NCSARequestLog { + private static final ThreadLocal buffers = + ThreadLocal.withInitial(() -> new StringBuilder(256)); + + private final DateCache dateCache; + + public ACSRequestLog() { + super(); + + TimeZone timeZone = TimeZone.getTimeZone("GMT"); + Locale locale = Locale.getDefault(); + dateCache = new DateCache(DEFAULT_DATE_FORMAT, locale, timeZone); + } + + @Override + public void log(Request request, Response response) { + String requestURI = StringUtils.cleanString(request.getOriginalURI()); + try { + StringBuilder sb = buffers.get(); + sb.setLength(0); + + InetAddress remoteAddress = ApiServlet.getClientAddress(request); + sb.append(remoteAddress.getHostAddress()) + .append(" - - [") + .append(dateCache.format(request.getTimeStamp())) + .append("] \"") + .append(request.getMethod()) + .append(" ") + .append(requestURI) + .append(" ") + .append(request.getProtocol()) + .append("\" ") + .append(response.getStatus()) + .append(" ") + .append(response.getHttpChannel().getBytesWritten()) // apply filter here? + .append(" \"-\" \"") + .append(request.getHeader("User-Agent")) + .append("\""); + + write(sb.toString()); + } catch (Exception e) { + LOG.warn("Unable to log request", e); + } + } + + @Override + protected void stop(LifeCycle lifeCycle) throws Exception { + buffers.remove(); + super.stop(lifeCycle); + } +} diff --git a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java index c6fd2ff24dc8..06477fff8986 100644 --- a/client/src/main/java/org/apache/cloudstack/ServerDaemon.java +++ b/client/src/main/java/org/apache/cloudstack/ServerDaemon.java @@ -32,7 +32,6 @@ import org.eclipse.jetty.jmx.MBeanContainer; import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConnectionFactory; -import org.eclipse.jetty.server.NCSARequestLog; import org.eclipse.jetty.server.RequestLog; import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Server; @@ -72,11 +71,6 @@ public class ServerDaemon implements Daemon { private static final String BIND_INTERFACE = "bind.interface"; private static final String CONTEXT_PATH = "context.path"; private static final String SESSION_TIMEOUT = "session.timeout"; - private static final String HTTP_ENABLE = "http.enable"; - private static final String HTTP_PORT = "http.port"; - private static final String HTTPS_ENABLE = "https.enable"; - private static final String HTTPS_PORT = "https.port"; - private static final String KEYSTORE_FILE = "https.keystore"; private static final String KEYSTORE_PASSWORD = "https.keystore.password"; private static final String WEBAPP_DIR = "webapp.dir"; private static final String ACCESS_LOG = "access.log"; @@ -84,6 +78,8 @@ public class ServerDaemon implements Daemon { private static final int DEFAULT_REQUEST_CONTENT_SIZE = 1048576; private static final String REQUEST_MAX_FORM_KEYS_KEY = "request.max.form.keys"; private static final int DEFAULT_REQUEST_MAX_FORM_KEYS = 5000; + private static final String THREADS_MIN = "threads.min"; + private static final String THREADS_MAX = "threads.max"; //////////////////////////////////////////////////////// /////////////// Server Configuration /////////////////// @@ -104,6 +100,8 @@ public class ServerDaemon implements Daemon { private String keystoreFile; private String keystorePassword; private String webAppLocation; + private int minThreads; + private int maxThreads; ////////////////////////////////////////////////// /////////////// Public methods /////////////////// @@ -134,17 +132,19 @@ public void init(final DaemonContext context) { } setBindInterface(properties.getProperty(BIND_INTERFACE, null)); setContextPath(properties.getProperty(CONTEXT_PATH, "/client")); - setHttpEnable(Boolean.valueOf(properties.getProperty(HTTP_ENABLE, "true"))); - setHttpPort(Integer.valueOf(properties.getProperty(HTTP_PORT, "8080"))); - setHttpsEnable(Boolean.valueOf(properties.getProperty(HTTPS_ENABLE, "false"))); - setHttpsPort(Integer.valueOf(properties.getProperty(HTTPS_PORT, "8443"))); - setKeystoreFile(properties.getProperty(KEYSTORE_FILE)); + setHttpEnable(Boolean.valueOf(properties.getProperty(ServerProperties.HTTP_ENABLE, "true"))); + setHttpPort(Integer.valueOf(properties.getProperty(ServerProperties.HTTP_PORT, "8080"))); + setHttpsEnable(Boolean.valueOf(properties.getProperty(ServerProperties.HTTPS_ENABLE, "false"))); + setHttpsPort(Integer.valueOf(properties.getProperty(ServerProperties.HTTPS_PORT, "8443"))); + setKeystoreFile(properties.getProperty(ServerProperties.KEYSTORE_FILE)); setKeystorePassword(properties.getProperty(KEYSTORE_PASSWORD)); setWebAppLocation(properties.getProperty(WEBAPP_DIR)); setAccessLogFile(properties.getProperty(ACCESS_LOG, "access.log")); setSessionTimeout(Integer.valueOf(properties.getProperty(SESSION_TIMEOUT, "30"))); setMaxFormContentSize(Integer.valueOf(properties.getProperty(REQUEST_CONTENT_SIZE_KEY, String.valueOf(DEFAULT_REQUEST_CONTENT_SIZE)))); setMaxFormKeys(Integer.valueOf(properties.getProperty(REQUEST_MAX_FORM_KEYS_KEY, String.valueOf(DEFAULT_REQUEST_MAX_FORM_KEYS)))); + setMinThreads(Integer.valueOf(properties.getProperty(THREADS_MIN, "10"))); + setMaxThreads(Integer.valueOf(properties.getProperty(THREADS_MAX, "500"))); } catch (final IOException e) { logger.warn("Failed to read configuration from server.properties file", e); } finally { @@ -162,8 +162,8 @@ public void init(final DaemonContext context) { public void start() throws Exception { // Thread pool final QueuedThreadPool threadPool = new QueuedThreadPool(); - threadPool.setMinThreads(10); - threadPool.setMaxThreads(500); + threadPool.setMinThreads(minThreads); + threadPool.setMaxThreads(maxThreads); // Jetty Server server = new Server(threadPool); @@ -299,7 +299,7 @@ private Pair createHandlers() { } private RequestLog createRequestLog() { - final NCSARequestLog log = new NCSARequestLog(); + final ACSRequestLog log = new ACSRequestLog(); final File logPath = new File(accessLogFile); final File parentFile = logPath.getParentFile(); if (parentFile != null) { @@ -376,4 +376,12 @@ public void setMaxFormContentSize(int maxFormContentSize) { public void setMaxFormKeys(int maxFormKeys) { this.maxFormKeys = maxFormKeys; } + + public void setMinThreads(int minThreads) { + this.minThreads = minThreads; + } + + public void setMaxThreads(int maxThreads) { + this.maxThreads = maxThreads; + } } diff --git a/cloud-cli/bindir/cloud-tool b/cloud-cli/bindir/cloud-tool index 410681a0dd5d..0f0815307a13 100755 --- a/cloud-cli/bindir/cloud-tool +++ b/cloud-cli/bindir/cloud-tool @@ -7,9 +7,9 @@ # 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 @@ -25,4 +25,5 @@ sys.path.append(os.path.dirname(os.path.dirname(__file__))) import cloudtool ret = cloudtool.main() -if ret: sys.exit(ret) +if ret: + sys.exit(ret) diff --git a/core/pom.xml b/core/pom.xml index 4689a1c86835..f91a330b7ba9 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT diff --git a/core/src/main/java/com/cloud/agent/api/CheckConvertInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/CheckConvertInstanceCommand.java index fc066e5c5899..c46fb697a3c1 100644 --- a/core/src/main/java/com/cloud/agent/api/CheckConvertInstanceCommand.java +++ b/core/src/main/java/com/cloud/agent/api/CheckConvertInstanceCommand.java @@ -18,6 +18,8 @@ public class CheckConvertInstanceCommand extends Command { boolean checkWindowsGuestConversionSupport = false; + boolean useVddk = false; + String vddkLibDir; public CheckConvertInstanceCommand() { } @@ -26,6 +28,11 @@ public CheckConvertInstanceCommand(boolean checkWindowsGuestConversionSupport) { this.checkWindowsGuestConversionSupport = checkWindowsGuestConversionSupport; } + public CheckConvertInstanceCommand(boolean checkWindowsGuestConversionSupport, boolean useVddk) { + this.checkWindowsGuestConversionSupport = checkWindowsGuestConversionSupport; + this.useVddk = useVddk; + } + @Override public boolean executeInSequence() { return false; @@ -34,4 +41,20 @@ public boolean executeInSequence() { public boolean getCheckWindowsGuestConversionSupport() { return checkWindowsGuestConversionSupport; } + + public boolean isUseVddk() { + return useVddk; + } + + public void setUseVddk(boolean useVddk) { + this.useVddk = useVddk; + } + + public String getVddkLibDir() { + return vddkLibDir; + } + + public void setVddkLibDir(String vddkLibDir) { + this.vddkLibDir = vddkLibDir; + } } 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/CheckS2SVpnConnectionsAnswer.java b/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java index b299c602dde5..351702a048ca 100644 --- a/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/CheckS2SVpnConnectionsAnswer.java @@ -25,7 +25,6 @@ public class CheckS2SVpnConnectionsAnswer extends Answer { Map ipToConnected; Map ipToDetail; - String details; protected CheckS2SVpnConnectionsAnswer() { ipToConnected = new HashMap(); diff --git a/core/src/main/java/com/cloud/agent/api/CleanupConvertedInstanceDisksCommand.java b/core/src/main/java/com/cloud/agent/api/CleanupConvertedInstanceDisksCommand.java new file mode 100644 index 000000000000..00373ec75364 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/CleanupConvertedInstanceDisksCommand.java @@ -0,0 +1,57 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import com.cloud.agent.api.to.DataStoreTO; + +/** + * This command is used to cleanup the converted instance disks from the storage pool: vmVolumesStore and the prefix: vmVolumesPrefix. + */ +public class CleanupConvertedInstanceDisksCommand extends Command { + + private DataStoreTO vmVolumesStore; + private String vmVolumesPrefix; + + public CleanupConvertedInstanceDisksCommand(DataStoreTO vmVolumesStore, String vmVolumesPrefix) { + this.vmVolumesStore = vmVolumesStore; + this.vmVolumesPrefix = vmVolumesPrefix; + } + + public DataStoreTO getVmVolumesStore() { + return vmVolumesStore; + } + + public void setVmVolumesStore(DataStoreTO vmVolumesStore) { + this.vmVolumesStore = vmVolumesStore; + } + + public String getVmVolumesPrefix() { + return vmVolumesPrefix; + } + + public void setVmVolumesPrefix(String vmVolumesPrefix) { + this.vmVolumesPrefix = vmVolumesPrefix; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/ConsoleAccessAuthenticationCommand.java b/core/src/main/java/com/cloud/agent/api/ConsoleAccessAuthenticationCommand.java index 683d4afd5b2f..ac6f15ec4c32 100644 --- a/core/src/main/java/com/cloud/agent/api/ConsoleAccessAuthenticationCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ConsoleAccessAuthenticationCommand.java @@ -27,6 +27,7 @@ public class ConsoleAccessAuthenticationCommand extends AgentControlCommand { private String _sid; private String _ticket; private String sessionUuid; + private String clientAddress; private boolean _isReauthenticating; @@ -35,13 +36,14 @@ public ConsoleAccessAuthenticationCommand() { } public ConsoleAccessAuthenticationCommand(String host, String port, String vmId, String sid, String ticket, - String sessiontkn) { + String sessiontkn, String clientAddress) { _host = host; _port = port; _vmId = vmId; _sid = sid; _ticket = ticket; sessionUuid = sessiontkn; + this.clientAddress = clientAddress; } public String getHost() { @@ -79,4 +81,12 @@ public String getSessionUuid() { public void setSessionUuid(String sessionUuid) { this.sessionUuid = sessionUuid; } + + public String getClientAddress() { + return clientAddress; + } + + public void setClientAddress(String clientAddress) { + this.clientAddress = clientAddress; + } } diff --git a/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java index f938d0ac55d7..38e0dca7736c 100644 --- a/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ConvertInstanceCommand.java @@ -23,30 +23,41 @@ public class ConvertInstanceCommand extends Command { private RemoteInstanceTO sourceInstance; + private String originalVMName; private Hypervisor.HypervisorType destinationHypervisorType; private DataStoreTO conversionTemporaryLocation; private String templateDirOnConversionLocation; private boolean checkConversionSupport; private boolean exportOvfToConversionLocation; private int threadsCountToExportOvf = 0; + private String extraParams; + private boolean useVddk; + private String vddkLibDir; + private String vddkTransports; + private String vddkThumbprint; public ConvertInstanceCommand() { } public ConvertInstanceCommand(RemoteInstanceTO sourceInstance, Hypervisor.HypervisorType destinationHypervisorType, DataStoreTO conversionTemporaryLocation, - String templateDirOnConversionLocation, boolean checkConversionSupport, boolean exportOvfToConversionLocation) { + String templateDirOnConversionLocation, boolean checkConversionSupport, boolean exportOvfToConversionLocation, String sourceVMName) { this.sourceInstance = sourceInstance; this.destinationHypervisorType = destinationHypervisorType; this.conversionTemporaryLocation = conversionTemporaryLocation; this.templateDirOnConversionLocation = templateDirOnConversionLocation; this.checkConversionSupport = checkConversionSupport; this.exportOvfToConversionLocation = exportOvfToConversionLocation; + this.originalVMName = sourceVMName; } public RemoteInstanceTO getSourceInstance() { return sourceInstance; } + public String getOriginalVMName() { + return originalVMName; + } + public Hypervisor.HypervisorType getDestinationHypervisorType() { return destinationHypervisorType; } @@ -75,6 +86,46 @@ public void setThreadsCountToExportOvf(int threadsCountToExportOvf) { this.threadsCountToExportOvf = threadsCountToExportOvf; } + public String getExtraParams() { + return extraParams; + } + + public void setExtraParams(String extraParams) { + this.extraParams = extraParams; + } + + public boolean isUseVddk() { + return useVddk; + } + + public void setUseVddk(boolean useVddk) { + this.useVddk = useVddk; + } + + public String getVddkLibDir() { + return vddkLibDir; + } + + public void setVddkLibDir(String vddkLibDir) { + this.vddkLibDir = vddkLibDir; + } + + public String getVddkTransports() { + return vddkTransports; + } + + public void setVddkTransports(String vddkTransports) { + this.vddkTransports = vddkTransports; + } + + public String getVddkThumbprint() { + return vddkThumbprint; + } + + public void setVddkThumbprint(String vddkThumbprint) { + this.vddkThumbprint = vddkThumbprint; + } + @Override public boolean executeInSequence() { return false; diff --git a/core/src/main/java/com/cloud/agent/api/ConvertSnapshotAnswer.java b/core/src/main/java/com/cloud/agent/api/ConvertSnapshotAnswer.java new file mode 100644 index 000000000000..c19a061f736c --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/ConvertSnapshotAnswer.java @@ -0,0 +1,36 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.cloud.agent.api; + +import org.apache.cloudstack.storage.to.SnapshotObjectTO; + +public class ConvertSnapshotAnswer extends Answer { + + private SnapshotObjectTO snapshotObjectTO; + + public ConvertSnapshotAnswer(SnapshotObjectTO snapshotObjectTO) { + super(null); + this.snapshotObjectTO = snapshotObjectTO; + } + + public SnapshotObjectTO getSnapshotObjectTO() { + return snapshotObjectTO; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/ConvertSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/ConvertSnapshotCommand.java new file mode 100644 index 000000000000..e456d4b6b122 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/ConvertSnapshotCommand.java @@ -0,0 +1,42 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.cloud.agent.api; + +import org.apache.cloudstack.storage.to.SnapshotObjectTO; + +public class ConvertSnapshotCommand extends Command { + + public static final String TEMP_SNAPSHOT_NAME = "_temp"; + + SnapshotObjectTO snapshotObjectTO; + + public SnapshotObjectTO getSnapshotObjectTO() { + return snapshotObjectTO; + } + + public ConvertSnapshotCommand(SnapshotObjectTO snapshotObjectTO) { + this.snapshotObjectTO = snapshotObjectTO; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/DeleteStoragePoolCommand.java b/core/src/main/java/com/cloud/agent/api/DeleteStoragePoolCommand.java index 969dd2eb801c..84ca39f15464 100644 --- a/core/src/main/java/com/cloud/agent/api/DeleteStoragePoolCommand.java +++ b/core/src/main/java/com/cloud/agent/api/DeleteStoragePoolCommand.java @@ -21,10 +21,10 @@ import java.io.File; import java.util.Map; -import java.util.UUID; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.storage.StoragePool; +import com.cloud.utils.UuidUtils; public class DeleteStoragePoolCommand extends Command { public static final String DATASTORE_NAME = "datastoreName"; @@ -49,7 +49,7 @@ public DeleteStoragePoolCommand(StoragePool pool, String localPath) { } public DeleteStoragePoolCommand(StoragePool pool) { - this(pool, LOCAL_PATH_PREFIX + File.separator + UUID.nameUUIDFromBytes((pool.getHostAddress() + pool.getPath()).getBytes())); + this(pool, LOCAL_PATH_PREFIX + File.separator + UuidUtils.nameUUIDFromBytes((pool.getHostAddress() + pool.getPath()).getBytes())); } public void setPool(StoragePool pool) { diff --git a/core/src/main/java/com/cloud/agent/api/GetExternalConsoleAnswer.java b/core/src/main/java/com/cloud/agent/api/GetExternalConsoleAnswer.java new file mode 100644 index 000000000000..e913d6f0d3a1 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/GetExternalConsoleAnswer.java @@ -0,0 +1,68 @@ +// 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.api; + +public class GetExternalConsoleAnswer extends Answer { + + private String url; + private String host; + private Integer port; + @LogLevel(LogLevel.Log4jLevel.Off) + private String password; + private String protocol; + private boolean passwordOneTimeUseOnly; + + public GetExternalConsoleAnswer(Command command, String details) { + super(command, false, details); + } + + public GetExternalConsoleAnswer(Command command, String url, String host, Integer port, String password, + boolean passwordOneTimeUseOnly, String protocol) { + super(command, true, ""); + this.url = url; + this.host = host; + this.port = port; + this.password = password; + this.passwordOneTimeUseOnly = passwordOneTimeUseOnly; + this.protocol = protocol; + } + + public String getUrl() { + return url; + } + + public String getHost() { + return host; + } + + public Integer getPort() { + return port; + } + + public String getPassword() { + return password; + } + + public String getProtocol() { + return protocol; + } + + public boolean isPasswordOneTimeUseOnly() { + return passwordOneTimeUseOnly; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/GetExternalConsoleCommand.java b/core/src/main/java/com/cloud/agent/api/GetExternalConsoleCommand.java new file mode 100644 index 000000000000..fc2134f631fb --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/GetExternalConsoleCommand.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package com.cloud.agent.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public class GetExternalConsoleCommand extends Command { + String vmName; + VirtualMachineTO vm; + protected boolean executeInSequence; + + public GetExternalConsoleCommand(String vmName, VirtualMachineTO vm) { + this.vmName = vmName; + this.vm = vm; + this.executeInSequence = false; + } + + public String getVmName() { + return this.vmName; + } + + public void setVirtualMachine(VirtualMachineTO vm) { + this.vm = vm; + } + + public VirtualMachineTO getVirtualMachine() { + return vm; + } + + @Override + public boolean executeInSequence() { + return executeInSequence; + } + + public void setExecuteInSequence(boolean executeInSequence) { + this.executeInSequence = executeInSequence; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java b/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java index 8b3cd44e207f..5bf70ed086f6 100644 --- a/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/GetGPUStatsAnswer.java @@ -19,7 +19,9 @@ package com.cloud.agent.api; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import com.cloud.agent.api.LogLevel.Log4jLevel; @@ -27,6 +29,7 @@ public class GetGPUStatsAnswer extends Answer { private HashMap> groupDetails; + private List gpuDevices = new ArrayList<>(); public GetGPUStatsAnswer(final GetGPUStatsCommand cmd, final HashMap> groupDetails) { super(cmd); @@ -37,7 +40,21 @@ public GetGPUStatsAnswer(final GetGPUStatsCommand cmd, final boolean success, fi super(cmd, success, details); } + public GetGPUStatsAnswer(final GetGPUStatsCommand cmd, final List gpuDevices) { + super(cmd); + this.gpuDevices = gpuDevices; + } + + public HashMap> getGroupDetails() { return groupDetails; } + + public List getGpuDevices() { + return gpuDevices; + } + + public void setGpuDevices(List gpuDevices) { + this.gpuDevices = gpuDevices; + } } diff --git a/core/src/main/java/com/cloud/agent/api/HandleCksIsoCommand.java b/core/src/main/java/com/cloud/agent/api/HandleCksIsoCommand.java new file mode 100644 index 000000000000..16942bb05d44 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/HandleCksIsoCommand.java @@ -0,0 +1,34 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.agent.api; + +import com.cloud.agent.api.routing.NetworkElementCommand; + +public class HandleCksIsoCommand extends NetworkElementCommand { + + private boolean mountCksIso; + + public HandleCksIsoCommand(boolean mountCksIso) { + this.mountCksIso = mountCksIso; + } + + public boolean isMountCksIso() { + return mountCksIso; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java index 9d50e852ced4..eadfa6556f86 100644 --- a/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ImportConvertedInstanceCommand.java @@ -27,17 +27,20 @@ public class ImportConvertedInstanceCommand extends Command { private List destinationStoragePools; private DataStoreTO conversionTemporaryLocation; private String temporaryConvertUuid; + private boolean forceConvertToPool; public ImportConvertedInstanceCommand() { } public ImportConvertedInstanceCommand(RemoteInstanceTO sourceInstance, List destinationStoragePools, - DataStoreTO conversionTemporaryLocation, String temporaryConvertUuid) { + DataStoreTO conversionTemporaryLocation, String temporaryConvertUuid, + boolean forceConvertToPool) { this.sourceInstance = sourceInstance; this.destinationStoragePools = destinationStoragePools; this.conversionTemporaryLocation = conversionTemporaryLocation; this.temporaryConvertUuid = temporaryConvertUuid; + this.forceConvertToPool = forceConvertToPool; } public RemoteInstanceTO getSourceInstance() { @@ -56,6 +59,10 @@ public String getTemporaryConvertUuid() { return temporaryConvertUuid; } + public boolean isForceConvertToPool() { + return forceConvertToPool; + } + @Override public boolean executeInSequence() { return false; diff --git a/core/src/main/java/com/cloud/agent/api/MigrateAgentConnectionAnswer.java b/core/src/main/java/com/cloud/agent/api/MigrateAgentConnectionAnswer.java new file mode 100644 index 000000000000..33d32c7f6ccb --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/MigrateAgentConnectionAnswer.java @@ -0,0 +1,38 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +public class MigrateAgentConnectionAnswer extends Answer { + public MigrateAgentConnectionAnswer() { + } + + public MigrateAgentConnectionAnswer(boolean result) { + this.result = result; + } + + public MigrateAgentConnectionAnswer(String details) { + this.result = false; + this.details = details; + } + + public MigrateAgentConnectionAnswer(MigrateAgentConnectionCommand cmd, boolean result) { + super(cmd, result, null); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/MigrateAgentConnectionCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateAgentConnectionCommand.java new file mode 100644 index 000000000000..9471a68669fe --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/MigrateAgentConnectionCommand.java @@ -0,0 +1,61 @@ +// +// 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.api; + +import java.util.List; + +public class MigrateAgentConnectionCommand extends Command { + private List msList; + private List avoidMsList; + private String lbAlgorithm; + private Long lbCheckInterval; + + public MigrateAgentConnectionCommand() { + } + + public MigrateAgentConnectionCommand(final List msList, final List avoidMsList, final String lbAlgorithm, final Long lbCheckInterval) { + super(); + this.msList = msList; + this.avoidMsList = avoidMsList; + this.lbAlgorithm = lbAlgorithm; + this.lbCheckInterval = lbCheckInterval; + } + + public List getMsList() { + return msList; + } + + public List getAvoidMsList() { + return avoidMsList; + } + + public String getLbAlgorithm() { + return lbAlgorithm; + } + + public Long getLbCheckInterval() { + return lbCheckInterval; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/MigrateBackupsBetweenSecondaryStoragesCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateBackupsBetweenSecondaryStoragesCommand.java new file mode 100644 index 000000000000..7794b4a53083 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/MigrateBackupsBetweenSecondaryStoragesCommand.java @@ -0,0 +1,41 @@ +// 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.api; + +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; + +import java.util.List; + +public class MigrateBackupsBetweenSecondaryStoragesCommand extends MigrateBetweenSecondaryStoragesCommand { + + List> backupChain; + + public MigrateBackupsBetweenSecondaryStoragesCommand() { + } + + public MigrateBackupsBetweenSecondaryStoragesCommand(List> backupChain, DataStoreTO srcDataStore, DataStoreTO destDataStore) { + super(srcDataStore, destDataStore); + this.backupChain = backupChain; + } + + public List> getBackupChain() { + return backupChain; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/MigrateBetweenSecondaryStoragesCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateBetweenSecondaryStoragesCommand.java new file mode 100644 index 000000000000..48dc7e2c85f4 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/MigrateBetweenSecondaryStoragesCommand.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import com.cloud.agent.api.to.DataStoreTO; + +public abstract class MigrateBetweenSecondaryStoragesCommand extends Command { + + DataStoreTO srcDataStore; + DataStoreTO destDataStore; + + public MigrateBetweenSecondaryStoragesCommand() { + } + + public MigrateBetweenSecondaryStoragesCommand(DataStoreTO srcDataStore, DataStoreTO destDataStore) { + this.srcDataStore = srcDataStore; + this.destDataStore = destDataStore; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public DataStoreTO getSrcDataStore() { + return srcDataStore; + } + + public DataStoreTO getDestDataStore() { + return destDataStore; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/MigrateBetweenSecondaryStoragesCommandAnswer.java b/core/src/main/java/com/cloud/agent/api/MigrateBetweenSecondaryStoragesCommandAnswer.java new file mode 100644 index 000000000000..fd303093e092 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/MigrateBetweenSecondaryStoragesCommandAnswer.java @@ -0,0 +1,41 @@ +// +// 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.api; + +import com.cloud.utils.Pair; + +import java.util.List; + +public class MigrateBetweenSecondaryStoragesCommandAnswer extends Answer { + + List> migratedResourcesIdAndCheckpointPath; + + public MigrateBetweenSecondaryStoragesCommandAnswer() { + } + + public MigrateBetweenSecondaryStoragesCommandAnswer(MigrateBetweenSecondaryStoragesCommand cmd, boolean success, String result, List> migratedResourcesIdAndCheckpointPath) { + super(cmd, success, result); + this.migratedResourcesIdAndCheckpointPath = migratedResourcesIdAndCheckpointPath; + } + + public List> getMigratedResources() { + return migratedResourcesIdAndCheckpointPath; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java index 3acdb9c351b5..7196247ffc23 100644 --- a/core/src/main/java/com/cloud/agent/api/MigrateCommand.java +++ b/core/src/main/java/com/cloud/agent/api/MigrateCommand.java @@ -26,22 +26,24 @@ import com.cloud.agent.api.to.DpdkTO; import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.storage.Storage; public class MigrateCommand extends Command { private String vmName; - private String destIp; + private String destinationIp; private Map migrateStorage; private boolean migrateStorageManaged; private boolean migrateNonSharedInc; private boolean autoConvergence; private String hostGuid; - private boolean isWindows; - private VirtualMachineTO vmTO; + private boolean windows; + private VirtualMachineTO virtualMachine; private boolean executeInSequence = false; private List migrateDiskInfoList = new ArrayList<>(); private Map dpdkInterfaceMapping = new HashMap<>(); private int newVmCpuShares; + private boolean clvmCrossPoolMigration; Map vlanToPersistenceMap = new HashMap<>(); @@ -64,11 +66,11 @@ public void setVlanToPersistenceMap(Map vlanToPersistenceMap) { protected MigrateCommand() { } - public MigrateCommand(String vmName, String destIp, boolean isWindows, VirtualMachineTO vmTO, boolean executeInSequence) { + public MigrateCommand(String vmName, String destinationIp, boolean windows, VirtualMachineTO virtualMachine, boolean executeInSequence) { this.vmName = vmName; - this.destIp = destIp; - this.isWindows = isWindows; - this.vmTO = vmTO; + this.destinationIp = destinationIp; + this.windows = windows; + this.virtualMachine = virtualMachine; this.executeInSequence = executeInSequence; } @@ -105,15 +107,15 @@ public boolean isAutoConvergence() { } public boolean isWindows() { - return isWindows; + return windows; } public VirtualMachineTO getVirtualMachine() { - return vmTO; + return virtualMachine; } public String getDestinationIp() { - return destIp; + return destinationIp; } public String getVmName() { @@ -149,6 +151,14 @@ public void setNewVmCpuShares(int newVmCpuShares) { this.newVmCpuShares = newVmCpuShares; } + public boolean isClvmCrossPoolMigration() { + return clvmCrossPoolMigration; + } + + public void setClvmCrossPoolMigration(boolean clvmCrossPoolMigration) { + this.clvmCrossPoolMigration = clvmCrossPoolMigration; + } + public static class MigrateDiskInfo { public enum DiskType { FILE, BLOCK; @@ -184,6 +194,8 @@ public String toString() { private final String sourceText; private final String backingStoreText; private boolean isSourceDiskOnStorageFileSystem; + private Storage.StoragePoolType sourcePoolType; + private Storage.StoragePoolType destPoolType; public MigrateDiskInfo(final String serialNumber, final DiskType diskType, final DriverType driverType, final Source source, final String sourceText) { this.serialNumber = serialNumber; @@ -232,5 +244,26 @@ public boolean isSourceDiskOnStorageFileSystem() { public void setSourceDiskOnStorageFileSystem(boolean isDiskOnFileSystemStorage) { this.isSourceDiskOnStorageFileSystem = isDiskOnFileSystemStorage; } + + public Storage.StoragePoolType getSourcePoolType() { + return sourcePoolType; + } + + public void setSourcePoolType(Storage.StoragePoolType sourcePoolType) { + this.sourcePoolType = sourcePoolType; + } + + public Storage.StoragePoolType getDestPoolType() { + return destPoolType; + } + + public void setDestPoolType(Storage.StoragePoolType destPoolType) { + this.destPoolType = destPoolType; + } + } + + @Override + public boolean isReconcile() { + return true; } } diff --git a/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolAnswer.java b/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolAnswer.java index 9f7b10595126..275468214f51 100644 --- a/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolAnswer.java @@ -50,6 +50,10 @@ public ModifyStoragePoolAnswer(final Command command, final boolean success, fin super(command, success, details); } + public ModifyStoragePoolAnswer(ModifyStoragePoolCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + public void setPoolInfo(StoragePoolInfo poolInfo) { this.poolInfo = poolInfo; } diff --git a/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolCommand.java b/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolCommand.java index 06940266b538..a72000d73995 100644 --- a/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ModifyStoragePoolCommand.java @@ -21,10 +21,10 @@ import java.io.File; import java.util.Map; -import java.util.UUID; import com.cloud.agent.api.to.StorageFilerTO; import com.cloud.storage.StoragePool; +import com.cloud.utils.UuidUtils; public class ModifyStoragePoolCommand extends Command { public static final String LOCAL_PATH_PREFIX = "/mnt/"; @@ -47,11 +47,11 @@ public ModifyStoragePoolCommand(boolean add, StoragePool pool, String localPath, } public ModifyStoragePoolCommand(boolean add, StoragePool pool, Map details) { - this(add, pool, LOCAL_PATH_PREFIX + File.separator + UUID.nameUUIDFromBytes((pool.getHostAddress() + pool.getPath()).getBytes()), details); + this(add, pool, LOCAL_PATH_PREFIX + File.separator + UuidUtils.nameUUIDFromBytes((pool.getHostAddress() + pool.getPath()).getBytes()), details); } public ModifyStoragePoolCommand(boolean add, StoragePool pool) { - this(add, pool, LOCAL_PATH_PREFIX + File.separator + UUID.nameUUIDFromBytes((pool.getHostAddress() + pool.getPath()).getBytes())); + this(add, pool, LOCAL_PATH_PREFIX + File.separator + UuidUtils.nameUUIDFromBytes((pool.getHostAddress() + pool.getPath()).getBytes())); } public boolean getAdd() { diff --git a/core/src/main/java/com/cloud/agent/api/PingAnswer.java b/core/src/main/java/com/cloud/agent/api/PingAnswer.java index 6353b121583a..22eb4443f8a4 100644 --- a/core/src/main/java/com/cloud/agent/api/PingAnswer.java +++ b/core/src/main/java/com/cloud/agent/api/PingAnswer.java @@ -19,18 +19,25 @@ package com.cloud.agent.api; +import java.util.ArrayList; +import java.util.List; + public class PingAnswer extends Answer { private PingCommand _command = null; private boolean sendStartup = false; + private List avoidMsList; + + private List reconcileCommands = new ArrayList<>(); protected PingAnswer() { } - public PingAnswer(PingCommand cmd, boolean sendStartup) { + public PingAnswer(PingCommand cmd, List avoidMsList, boolean sendStartup) { super(cmd); _command = cmd; this.sendStartup = sendStartup; + this.avoidMsList = avoidMsList; } public PingCommand getCommand() { @@ -44,4 +51,20 @@ public boolean isSendStartup() { public void setSendStartup(boolean sendStartup) { this.sendStartup = sendStartup; } + + public List getReconcileCommands() { + return reconcileCommands; + } + + public void setReconcileCommands(List reconcileCommands) { + this.reconcileCommands = reconcileCommands; + } + + public void addReconcileCommand(String reconcileCommand) { + this.reconcileCommands.add(reconcileCommand); + } + + public List getAvoidMsList() { + return avoidMsList; + } } diff --git a/core/src/main/java/com/cloud/agent/api/PingCommand.java b/core/src/main/java/com/cloud/agent/api/PingCommand.java index 4192fc2e7474..4ae64da89c3c 100644 --- a/core/src/main/java/com/cloud/agent/api/PingCommand.java +++ b/core/src/main/java/com/cloud/agent/api/PingCommand.java @@ -20,11 +20,14 @@ package com.cloud.agent.api; import com.cloud.host.Host; +import org.apache.cloudstack.command.CommandInfo; public class PingCommand extends Command { Host.Type hostType; long hostId; boolean outOfBand; + @LogLevel(LogLevel.Log4jLevel.Trace) + private CommandInfo[] commandInfos = new CommandInfo[] {}; protected PingCommand() { } @@ -78,4 +81,12 @@ public int hashCode() { result = 31 * result + (int) (hostId ^ (hostId >>> 32)); return result; } + + public CommandInfo[] getCommandInfos() { + return commandInfos; + } + + public void setCommandInfos(CommandInfo[] commandInfos) { + this.commandInfos = commandInfos; + } } diff --git a/core/src/main/java/com/cloud/agent/api/PostMigrationAnswer.java b/core/src/main/java/com/cloud/agent/api/PostMigrationAnswer.java new file mode 100644 index 000000000000..24fdf8402029 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PostMigrationAnswer.java @@ -0,0 +1,42 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +/** + * Answer for PostMigrationCommand. + * Indicates success or failure of post-migration operations on the destination host. + */ +public class PostMigrationAnswer extends Answer { + + protected PostMigrationAnswer() { + } + + public PostMigrationAnswer(PostMigrationCommand cmd, String detail) { + super(cmd, false, detail); + } + + public PostMigrationAnswer(PostMigrationCommand cmd, Exception ex) { + super(cmd, ex); + } + + public PostMigrationAnswer(PostMigrationCommand cmd) { + super(cmd, true, null); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PostMigrationCommand.java b/core/src/main/java/com/cloud/agent/api/PostMigrationCommand.java new file mode 100644 index 000000000000..938000c3593c --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PostMigrationCommand.java @@ -0,0 +1,59 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +/** + * PostMigrationCommand is sent to the destination host after a successful VM migration. + * It performs post-migration tasks such as: + * - Claiming exclusive locks on CLVM volumes (converting from shared to exclusive mode) + * - Other post-migration cleanup operations + */ +public class PostMigrationCommand extends Command { + private VirtualMachineTO vm; + private String vmName; + + protected PostMigrationCommand() { + } + + public PostMigrationCommand(VirtualMachineTO vm, String vmName) { + this.vm = vm; + this.vmName = vmName; + } + + public VirtualMachineTO getVirtualMachine() { + return vm; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return true; + } + + @Override + public boolean isBypassHostMaintenance() { + return true; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PreMigrationCommand.java b/core/src/main/java/com/cloud/agent/api/PreMigrationCommand.java new file mode 100644 index 000000000000..3ff30391eaef --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PreMigrationCommand.java @@ -0,0 +1,61 @@ +// +// 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.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +/** + * PreMigrationCommand is sent to the source host before VM migration starts. + * It performs pre-migration tasks such as: + * - Converting CLVM volume exclusive locks to shared mode so destination host can access them + * - Other pre-migration preparation operations on the source host + * + * This command runs on the SOURCE host before PrepareForMigrationCommand runs on the DESTINATION host. + */ +public class PreMigrationCommand extends Command { + private VirtualMachineTO vm; + private String vmName; + + protected PreMigrationCommand() { + } + + public PreMigrationCommand(VirtualMachineTO vm, String vmName) { + this.vm = vm; + this.vmName = vmName; + } + + public VirtualMachineTO getVirtualMachine() { + return vm; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return true; + } + + @Override + public boolean isBypassHostMaintenance() { + return true; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java new file mode 100644 index 000000000000..b94d18c537ea --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningAnswer.java @@ -0,0 +1,51 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.agent.api; + +import java.util.Map; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public class PrepareExternalProvisioningAnswer extends Answer { + + Map serverDetails; + VirtualMachineTO virtualMachineTO; + + public PrepareExternalProvisioningAnswer() { + super(); + } + + public PrepareExternalProvisioningAnswer(PrepareExternalProvisioningCommand cmd, Map externalDetails, VirtualMachineTO virtualMachineTO, String details) { + super(cmd, true, details); + this.serverDetails = externalDetails; + this.virtualMachineTO = virtualMachineTO; + } + + public PrepareExternalProvisioningAnswer(PrepareExternalProvisioningCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + + public Map getServerDetails() { + return serverDetails; + } + + public VirtualMachineTO getVirtualMachineTO() { + return virtualMachineTO; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java new file mode 100644 index 000000000000..44f57607eba9 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/PrepareExternalProvisioningCommand.java @@ -0,0 +1,39 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.agent.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public class PrepareExternalProvisioningCommand extends Command { + + VirtualMachineTO virtualMachineTO; + + public PrepareExternalProvisioningCommand(VirtualMachineTO vmTO) { + this.virtualMachineTO = vmTO; + } + + public VirtualMachineTO getVirtualMachineTO() { + return virtualMachineTO; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java b/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java index ed337885beed..21c4e7b97d03 100644 --- a/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java +++ b/core/src/main/java/com/cloud/agent/api/PropagateResourceEventCommand.java @@ -24,6 +24,8 @@ public class PropagateResourceEventCommand extends Command { long hostId; ResourceState.Event event; + boolean forced; + boolean forceDeleteStorage; protected PropagateResourceEventCommand() { @@ -34,6 +36,13 @@ public PropagateResourceEventCommand(long hostId, ResourceState.Event event) { this.event = event; } + public PropagateResourceEventCommand(long hostId, ResourceState.Event event, boolean forced, boolean forceDeleteStorage) { + this.hostId = hostId; + this.event = event; + this.forced = forced; + this.forceDeleteStorage = forceDeleteStorage; + } + public long getHostId() { return hostId; } @@ -42,6 +51,14 @@ public ResourceState.Event getEvent() { return event; } + public boolean isForced() { + return forced; + } + + public boolean isForceDeleteStorage() { + return forceDeleteStorage; + } + @Override public boolean executeInSequence() { // TODO Auto-generated method stub diff --git a/core/src/main/java/com/cloud/agent/api/ReadyCommand.java b/core/src/main/java/com/cloud/agent/api/ReadyCommand.java index e2d974e38786..ee61fee66c63 100644 --- a/core/src/main/java/com/cloud/agent/api/ReadyCommand.java +++ b/core/src/main/java/com/cloud/agent/api/ReadyCommand.java @@ -26,20 +26,21 @@ public class ReadyCommand extends Command { private String _details; - public ReadyCommand() { - super(); - } - private Long dcId; private Long hostId; private String hostUuid; private String hostName; private List msHostList; + private List avoidMsHostList; private String lbAlgorithm; private Long lbCheckInterval; private Boolean enableHumanReadableSizes; private String arch; + public ReadyCommand() { + super(); + } + public ReadyCommand(Long dcId) { super(); this.dcId = dcId; @@ -90,6 +91,14 @@ public void setMsHostList(List msHostList) { this.msHostList = msHostList; } + public List getAvoidMsHostList() { + return avoidMsHostList; + } + + public void setAvoidMsHostList(List avoidMsHostList) { + this.avoidMsHostList = avoidMsHostList; + } + public String getLbAlgorithm() { return lbAlgorithm; } diff --git a/core/src/main/java/com/cloud/agent/api/RecreateCheckpointsCommand.java b/core/src/main/java/com/cloud/agent/api/RecreateCheckpointsCommand.java new file mode 100644 index 000000000000..154b611be98f --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/RecreateCheckpointsCommand.java @@ -0,0 +1,49 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import java.util.List; + +public class RecreateCheckpointsCommand extends Command { + + private List volumes; + + private String vmName; + + public RecreateCheckpointsCommand(List volumes, String vmName) { + this.volumes = volumes; + this.vmName = vmName; + } + + public List getDisks() { + return volumes; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/RemoveBitmapCommand.java b/core/src/main/java/com/cloud/agent/api/RemoveBitmapCommand.java new file mode 100644 index 000000000000..90b62e3f17b9 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/RemoveBitmapCommand.java @@ -0,0 +1,46 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.agent.api; + +import org.apache.cloudstack.storage.to.SnapshotObjectTO; + +public class RemoveBitmapCommand extends Command { + + private SnapshotObjectTO snapshotObjectTO; + + private boolean isVmRunning; + + public RemoveBitmapCommand(SnapshotObjectTO snapshotObjectTO, boolean isVmRunning) { + this.snapshotObjectTO = snapshotObjectTO; + this.isVmRunning = isVmRunning; + } + + @Override + public boolean executeInSequence() { + return true; + } + + public SnapshotObjectTO getSnapshotObjectTO() { + return snapshotObjectTO; + } + + public boolean isVmRunning() { + return isVmRunning; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/RunCustomActionAnswer.java b/core/src/main/java/com/cloud/agent/api/RunCustomActionAnswer.java new file mode 100644 index 000000000000..1deb789c995f --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/RunCustomActionAnswer.java @@ -0,0 +1,32 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +public class RunCustomActionAnswer extends Answer { + + public RunCustomActionAnswer(RunCustomActionCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/RunCustomActionCommand.java b/core/src/main/java/com/cloud/agent/api/RunCustomActionCommand.java new file mode 100644 index 000000000000..113073ac5ee5 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/RunCustomActionCommand.java @@ -0,0 +1,61 @@ +// +// 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.api; + +import java.util.Map; + +import com.cloud.agent.api.to.VirtualMachineTO; + +public class RunCustomActionCommand extends Command { + + String actionName; + VirtualMachineTO vmTO; + Map parameters; + + public RunCustomActionCommand(String actionName) { + this.actionName = actionName; + this.setWait(5); + } + + public String getActionName() { + return actionName; + } + + public VirtualMachineTO getVmTO() { + return vmTO; + } + + public void setVmTO(VirtualMachineTO vmTO) { + this.vmTO = vmTO; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + @Override + public boolean executeInSequence() { + return false; + } +} 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/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java b/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java index 06583f2d0d3d..fef0ceed6b85 100644 --- a/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java +++ b/core/src/main/java/com/cloud/agent/api/SetupGuestNetworkCommand.java @@ -36,6 +36,7 @@ public class SetupGuestNetworkCommand extends NetworkElementCommand { String routerIpv6Gateway = null; String routerIpv6Cidr = null; boolean isVrGuestGateway = false; + long networkId; public NicTO getNic() { return nic; @@ -123,4 +124,12 @@ public boolean isVrGuestGateway() { public void setVrGuestGateway(boolean vrGuestGateway) { isVrGuestGateway = vrGuestGateway; } + + public long getNetworkId() { + return networkId; + } + + public void setNetworkId(long networkId) { + this.networkId = networkId; + } } diff --git a/core/src/main/java/com/cloud/agent/api/StartCommand.java b/core/src/main/java/com/cloud/agent/api/StartCommand.java index 24b0ac3787b5..ea763d6cd4bd 100644 --- a/core/src/main/java/com/cloud/agent/api/StartCommand.java +++ b/core/src/main/java/com/cloud/agent/api/StartCommand.java @@ -22,13 +22,15 @@ import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.host.Host; +import java.util.List; + /** */ public class StartCommand extends Command { VirtualMachineTO vm; String hostIp; boolean executeInSequence = false; - String secondaryStorage; + private List secondaryStorages; public VirtualMachineTO getVirtualMachine() { return vm; @@ -50,18 +52,18 @@ public StartCommand(VirtualMachineTO vm, Host host, boolean executeInSequence) { this.vm = vm; this.hostIp = host.getPrivateIpAddress(); this.executeInSequence = executeInSequence; - this.secondaryStorage = null; + this.secondaryStorages = null; } public String getHostIp() { return this.hostIp; } - public String getSecondaryStorage() { - return this.secondaryStorage; + public List getSecondaryStorages() { + return this.secondaryStorages; } - public void setSecondaryStorage(String secondary) { - this.secondaryStorage = secondary; + public void setSecondaryStorages(List secondary) { + this.secondaryStorages = secondary; } } diff --git a/core/src/main/java/com/cloud/agent/api/StartupCommand.java b/core/src/main/java/com/cloud/agent/api/StartupCommand.java index cca5e16b5854..7a18ba2dccc1 100644 --- a/core/src/main/java/com/cloud/agent/api/StartupCommand.java +++ b/core/src/main/java/com/cloud/agent/api/StartupCommand.java @@ -47,6 +47,7 @@ public class StartupCommand extends Command { String resourceName; String gatewayIpAddress; String msHostList; + boolean connectionTransferred; String arch; public StartupCommand(Host.Type type) { @@ -291,6 +292,14 @@ public void setMSHostList(String msHostList) { this.msHostList = msHostList; } + public boolean isConnectionTransferred() { + return connectionTransferred; + } + + public void setConnectionTransferred(boolean connectionTransferred) { + this.connectionTransferred = connectionTransferred; + } + public String getArch() { return arch; } diff --git a/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java b/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java index 286fced0c58a..068196aabe54 100644 --- a/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java +++ b/core/src/main/java/com/cloud/agent/api/StartupRoutingCommand.java @@ -45,6 +45,7 @@ public class StartupRoutingCommand extends StartupCommand { List hostTags = new ArrayList(); String hypervisorVersion; HashMap> groupDetails = new HashMap>(); + List gpuDevices = new ArrayList<>(); private Boolean hostHealthCheckResult; public StartupRoutingCommand() { @@ -179,7 +180,7 @@ public void setHostTags(List hostTags) { this.hostTags = hostTags; } - public HashMap> getGpuGroupDetails() { + public HashMap> getGpuGroupDetails() { return groupDetails; } @@ -187,6 +188,14 @@ public void setGpuGroupDetails(HashMap> g this.groupDetails = groupDetails; } + public List getGpuDevices() { + return gpuDevices; + } + + public void setGpuDevices(List gpuDevices) { + this.gpuDevices = gpuDevices; + } + public boolean getSupportsClonedVolumes() { return supportsClonedVolumes; } diff --git a/core/src/main/java/com/cloud/agent/api/StopCommand.java b/core/src/main/java/com/cloud/agent/api/StopCommand.java index 3923a35bd0a7..d07ffa2e31f7 100644 --- a/core/src/main/java/com/cloud/agent/api/StopCommand.java +++ b/core/src/main/java/com/cloud/agent/api/StopCommand.java @@ -19,14 +19,14 @@ package com.cloud.agent.api; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + import com.cloud.agent.api.to.DpdkTO; import com.cloud.agent.api.to.GPUDeviceTO; import com.cloud.vm.VirtualMachine; -import java.util.ArrayList; -import java.util.Map; -import java.util.List; - public class StopCommand extends RebootCommand { private boolean isProxy = false; private String urlPort = null; @@ -37,6 +37,7 @@ public class StopCommand extends RebootCommand { boolean forceStop = false; private Map dpdkInterfaceMapping; Map vlanToPersistenceMap; + boolean expungeVM = false; public Map getDpdkInterfaceMapping() { return dpdkInterfaceMapping; @@ -138,4 +139,12 @@ public Map getVlanToPersistenceMap() { public void setVlanToPersistenceMap(Map vlanToPersistenceMap) { this.vlanToPersistenceMap = vlanToPersistenceMap; } + + public boolean isExpungeVM() { + return expungeVM; + } + + public void setExpungeVM(boolean expungeVM) { + this.expungeVM = expungeVM; + } } diff --git a/core/src/main/java/com/cloud/agent/api/TransferAgentCommand.java b/core/src/main/java/com/cloud/agent/api/TransferAgentCommand.java index ab74d9bcf852..9c6b3b5fc592 100644 --- a/core/src/main/java/com/cloud/agent/api/TransferAgentCommand.java +++ b/core/src/main/java/com/cloud/agent/api/TransferAgentCommand.java @@ -25,6 +25,7 @@ public class TransferAgentCommand extends Command { protected long agentId; protected long futureOwner; protected long currentOwner; + protected boolean isConnectionTransfer; Event event; protected TransferAgentCommand() { @@ -37,6 +38,11 @@ public TransferAgentCommand(long agentId, long currentOwner, long futureOwner, E this.event = event; } + public TransferAgentCommand(long agentId, long currentOwner, long futureOwner, Event event, boolean isConnectionTransfer) { + this(agentId, currentOwner, futureOwner, event); + this.isConnectionTransfer = isConnectionTransfer; + } + public long getAgentId() { return agentId; } @@ -53,6 +59,10 @@ public long getCurrentOwner() { return currentOwner; } + public boolean isConnectionTransfer() { + return isConnectionTransfer; + } + @Override public boolean executeInSequence() { return false; diff --git a/core/src/main/java/com/cloud/agent/api/UnmanageInstanceAnswer.java b/core/src/main/java/com/cloud/agent/api/UnmanageInstanceAnswer.java new file mode 100644 index 000000000000..39a35d499906 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UnmanageInstanceAnswer.java @@ -0,0 +1,27 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.api; + +public class UnmanageInstanceAnswer extends Answer { + + public UnmanageInstanceAnswer(UnmanageInstanceCommand cmd, boolean success, String details) { + super(cmd, success, details); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UnmanageInstanceCommand.java b/core/src/main/java/com/cloud/agent/api/UnmanageInstanceCommand.java new file mode 100644 index 000000000000..dd504b9ea265 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UnmanageInstanceCommand.java @@ -0,0 +1,61 @@ +// +// 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.api; + +import com.cloud.agent.api.to.VirtualMachineTO; + +/** + */ +public class UnmanageInstanceCommand extends Command { + String instanceName; + boolean executeInSequence = false; + VirtualMachineTO vm; + boolean isConfigDriveAttached; + + @Override + public boolean executeInSequence() { + return executeInSequence; + } + + public UnmanageInstanceCommand(VirtualMachineTO vm) { + this.vm = vm; + this.instanceName = vm.getName(); + } + + public UnmanageInstanceCommand(String instanceName) { + this.instanceName = instanceName; + } + + public String getInstanceName() { + return instanceName; + } + + public VirtualMachineTO getVm() { + return vm; + } + + public boolean isConfigDriveAttached() { + return isConfigDriveAttached; + } + + public void setConfigDriveAttached(boolean configDriveAttached) { + isConfigDriveAttached = configDriveAttached; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java b/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java index bebd30ca519d..b98ce6fa345e 100644 --- a/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java +++ b/core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java @@ -19,18 +19,22 @@ package com.cloud.agent.api; +import java.util.Map; + import com.cloud.storage.Storage.StoragePoolType; public class UnprepareStorageClientCommand extends Command { private StoragePoolType poolType; private String poolUuid; + private Map details; public UnprepareStorageClientCommand() { } - public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid) { + public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid, Map details) { this.poolType = poolType; this.poolUuid = poolUuid; + this.details = details; } @Override @@ -45,4 +49,8 @@ public StoragePoolType getPoolType() { public String getPoolUuid() { return poolUuid; } + + public Map getDetails() { + return details; + } } diff --git a/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java b/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java new file mode 100644 index 000000000000..0af6d7fa3a3d --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UpdateVmNicAnswer.java @@ -0,0 +1,27 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.agent.api; + +public class UpdateVmNicAnswer extends Answer { + public UpdateVmNicAnswer() { + } + + public UpdateVmNicAnswer(UpdateVmNicCommand cmd, boolean success, String result) { + super(cmd, success, result); + } +} diff --git a/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java b/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java new file mode 100644 index 000000000000..a5c5faf32fed --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/UpdateVmNicCommand.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.agent.api; + +public class UpdateVmNicCommand extends Command { + + String nicMacAddress; + String instanceName; + Boolean enabled; + + @Override + public boolean executeInSequence() { + return true; + } + + protected UpdateVmNicCommand() { + } + + public UpdateVmNicCommand(String nicMacAdderss, String instanceName, Boolean enabled) { + this.nicMacAddress = nicMacAdderss; + this.instanceName = instanceName; + this.enabled = enabled; + } + + public String getNicMacAddress() { + return nicMacAddress; + } + + public String getVmName() { + return instanceName; + } + + public Boolean isEnabled() { + return enabled; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java b/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java index 7fb65fe15cf9..67ca447e3853 100644 --- a/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java +++ b/core/src/main/java/com/cloud/agent/api/routing/DhcpEntryCommand.java @@ -36,6 +36,7 @@ public class DhcpEntryCommand extends NetworkElementCommand { private boolean isDefault; boolean executeInSequence = false; boolean remove; + Long leaseTime; public boolean isRemove() { return remove; @@ -152,4 +153,12 @@ public boolean isDefault() { public void setDefault(boolean isDefault) { this.isDefault = isDefault; } + + public Long getLeaseTime() { + return leaseTime; + } + + public void setLeaseTime(Long leaseTime) { + this.leaseTime = leaseTime; + } } diff --git a/core/src/main/java/com/cloud/agent/api/routing/LoadBalancerConfigCommand.java b/core/src/main/java/com/cloud/agent/api/routing/LoadBalancerConfigCommand.java index d8cc74817d7b..96d73e11990d 100644 --- a/core/src/main/java/com/cloud/agent/api/routing/LoadBalancerConfigCommand.java +++ b/core/src/main/java/com/cloud/agent/api/routing/LoadBalancerConfigCommand.java @@ -36,6 +36,7 @@ public class LoadBalancerConfigCommand extends NetworkElementCommand { public String lbStatsAuth = "admin1:AdMiN123"; public String lbStatsUri = "/admin?stats"; public String maxconn = ""; + public Long idleTimeout = 50000L; /* 0=infinite, >0 = timeout in milliseconds */ public String lbProtocol; public boolean keepAliveEnabled = false; NicTO nic; @@ -50,7 +51,7 @@ public LoadBalancerConfigCommand(LoadBalancerTO[] loadBalancers, Long vpcId) { } public LoadBalancerConfigCommand(LoadBalancerTO[] loadBalancers, String publicIp, String guestIp, String privateIp, NicTO nic, Long vpcId, String maxconn, - boolean keepAliveEnabled) { + boolean keepAliveEnabled, Long idleTimeout) { this.loadBalancers = loadBalancers; this.lbStatsPublicIP = publicIp; this.lbStatsPrivateIP = privateIp; @@ -59,6 +60,7 @@ public LoadBalancerConfigCommand(LoadBalancerTO[] loadBalancers, String publicIp this.vpcId = vpcId; this.maxconn = maxconn; this.keepAliveEnabled = keepAliveEnabled; + this.idleTimeout = idleTimeout; } public NicTO getNic() { diff --git a/core/src/main/java/com/cloud/agent/api/routing/NetworkElementCommand.java b/core/src/main/java/com/cloud/agent/api/routing/NetworkElementCommand.java index 400b6bb80917..20d2ea0a443d 100644 --- a/core/src/main/java/com/cloud/agent/api/routing/NetworkElementCommand.java +++ b/core/src/main/java/com/cloud/agent/api/routing/NetworkElementCommand.java @@ -37,6 +37,7 @@ public abstract class NetworkElementCommand extends Command { public static final String ZONE_NETWORK_TYPE = "zone.network.type"; public static final String GUEST_BRIDGE = "guest.bridge"; public static final String VPC_PRIVATE_GATEWAY = "vpc.gateway.private"; + public static final String VPC_ID = "vpc.id"; public static final String FIREWALL_EGRESS_DEFAULT = "firewall.egress.default"; public static final String NETWORK_PUB_LAST_IP = "network.public.last.ip"; public static final String HYPERVISOR_HOST_PRIVATE_IP = "hypervisor.private.ip"; diff --git a/core/src/main/java/com/cloud/agent/api/routing/SetFirewallRulesCommand.java b/core/src/main/java/com/cloud/agent/api/routing/SetFirewallRulesCommand.java index c56f8d20fbe6..ff81ab7749c5 100644 --- a/core/src/main/java/com/cloud/agent/api/routing/SetFirewallRulesCommand.java +++ b/core/src/main/java/com/cloud/agent/api/routing/SetFirewallRulesCommand.java @@ -32,6 +32,7 @@ */ public class SetFirewallRulesCommand extends NetworkElementCommand { FirewallRuleTO[] rules; + Long vpcId; protected SetFirewallRulesCommand() { } @@ -40,10 +41,19 @@ public SetFirewallRulesCommand(List rules) { this.rules = rules.toArray(new FirewallRuleTO[rules.size()]); } + public SetFirewallRulesCommand(List rules, Long vpcId) { + this.rules = rules.toArray(new FirewallRuleTO[rules.size()]); + this.vpcId = vpcId; + } + public FirewallRuleTO[] getRules() { return rules; } + public Long getVpcId() { + return vpcId; + } + public String[][] generateFwRules() { String[][] result = new String[2][]; Set toAdd = new HashSet(); diff --git a/core/src/main/java/com/cloud/agent/api/storage/CreateDiskOnlyVmSnapshotAnswer.java b/core/src/main/java/com/cloud/agent/api/storage/CreateDiskOnlyVmSnapshotAnswer.java new file mode 100644 index 000000000000..9a571e34c7e6 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/storage/CreateDiskOnlyVmSnapshotAnswer.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +import java.util.Map; + +public class CreateDiskOnlyVmSnapshotAnswer extends Answer { + + protected Map mapVolumeToSnapshotSize; + + public CreateDiskOnlyVmSnapshotAnswer(Command command, boolean success, String details, Map mapVolumeToSnapshotSize) { + super(command, success, details); + this.mapVolumeToSnapshotSize = mapVolumeToSnapshotSize; + } + + public Map getMapVolumeToSnapshotSize() { + return mapVolumeToSnapshotSize; + } +} diff --git a/core/src/main/java/com/cloud/agent/api/storage/CreateDiskOnlyVmSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/storage/CreateDiskOnlyVmSnapshotCommand.java new file mode 100644 index 000000000000..da1b420625f8 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/storage/CreateDiskOnlyVmSnapshotCommand.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.cloud.agent.api.storage; + + +import com.cloud.agent.api.VMSnapshotBaseCommand; +import com.cloud.agent.api.VMSnapshotTO; +import com.cloud.utils.Pair; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import java.util.List; + +public class CreateDiskOnlyVmSnapshotCommand extends VMSnapshotBaseCommand { + + protected VirtualMachine.State vmState; + + List> volumeTosAndNewPaths; + + public CreateDiskOnlyVmSnapshotCommand(String vmName, VMSnapshotTO snapshot, List> volumeTosAndNewPaths, String guestOSType, VirtualMachine.State vmState) { + super(vmName, snapshot, null, guestOSType); + this.vmState = vmState; + this.volumeTosAndNewPaths = volumeTosAndNewPaths; + } + + public VirtualMachine.State getVmState() { + return vmState; + } + + public List> getVolumeTosAndNewPaths() { + return volumeTosAndNewPaths; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java b/core/src/main/java/com/cloud/agent/api/storage/DeleteDiskOnlyVmSnapshotCommand.java similarity index 61% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java rename to core/src/main/java/com/cloud/agent/api/storage/DeleteDiskOnlyVmSnapshotCommand.java index 835ac696f26c..bf7bdd597360 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDao.java +++ b/core/src/main/java/com/cloud/agent/api/storage/DeleteDiskOnlyVmSnapshotCommand.java @@ -16,21 +16,29 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule.dao; +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Command; + +import com.cloud.agent.api.to.DataTO; -import com.cloud.utils.db.GenericDao; -import org.apache.cloudstack.vm.schedule.VMScheduledJobVO; -import java.util.Date; import java.util.List; -public interface VMScheduledJobDao extends GenericDao { +public class DeleteDiskOnlyVmSnapshotCommand extends Command { - List listJobsToStart(Date currentTimestamp); + List snapshots; - int expungeJobsForSchedules(List scheduleId, Date dateAfter); + public DeleteDiskOnlyVmSnapshotCommand(List snapshots) { + this.snapshots = snapshots; + } - int expungeJobsBefore(Date currentTimestamp); + public List getSnapshots() { + return snapshots; + } - VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp); + @Override + public boolean executeInSequence() { + return false; + } } diff --git a/core/src/main/java/com/cloud/agent/api/storage/MergeDiskOnlyVmSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/storage/MergeDiskOnlyVmSnapshotCommand.java new file mode 100644 index 000000000000..1a47d97d5e25 --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/storage/MergeDiskOnlyVmSnapshotCommand.java @@ -0,0 +1,55 @@ +/* + * 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.api.storage; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.DeltaMergeTreeTO; + +import java.util.List; + +public class MergeDiskOnlyVmSnapshotCommand extends Command { + + private List snapshotMergeTreeToList; + private boolean isVmRunning; + private String vmName; + + public MergeDiskOnlyVmSnapshotCommand(List snapshotMergeTreeToList, boolean isVmRunning, String vmName) { + this.snapshotMergeTreeToList = snapshotMergeTreeToList; + this.isVmRunning = isVmRunning; + this.vmName = vmName; + } + + public List getDeltaMergeTreeToList() { + return snapshotMergeTreeToList; + } + + public boolean isVmRunning() { + return isVmRunning; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/core/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java b/core/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java index 9fed0f913e14..70375c30a1bb 100644 --- a/core/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java +++ b/core/src/main/java/com/cloud/agent/api/storage/MigrateVolumeCommand.java @@ -145,4 +145,9 @@ public int getWaitInMillSeconds() { } public String getChainInfo() { return chainInfo; } + + @Override + public boolean isReconcile() { + return true; + } } diff --git a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java b/core/src/main/java/com/cloud/agent/api/storage/RevertDiskOnlyVmSnapshotAnswer.java similarity index 56% rename from server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java rename to core/src/main/java/com/cloud/agent/api/storage/RevertDiskOnlyVmSnapshotAnswer.java index fa96a1c97b93..2ecf587d59d1 100644 --- a/server/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduler.java +++ b/core/src/main/java/com/cloud/agent/api/storage/RevertDiskOnlyVmSnapshotAnswer.java @@ -16,21 +16,23 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule; +package com.cloud.agent.api.storage; -import com.cloud.utils.component.Manager; -import com.cloud.utils.concurrency.Scheduler; -import org.apache.cloudstack.framework.config.ConfigKey; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.VolumeObjectTO; -import java.util.Date; import java.util.List; -public interface VMScheduler extends Manager, Scheduler { - ConfigKey VMScheduledJobExpireInterval = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "vmscheduler.jobs.expire.interval", "30", "VM Scheduler expire interval in days", true); +public class RevertDiskOnlyVmSnapshotAnswer extends Answer { + List volumeObjectTos; - void removeScheduledJobs(List vmScheduleIds); + public RevertDiskOnlyVmSnapshotAnswer(Command cmd, List volumeObjectTos) { + super(cmd, true, null); + this.volumeObjectTos = volumeObjectTos; + } - void updateScheduledJob(VMScheduleVO vmSchedule); - - Date scheduleNextJob(VMScheduleVO vmSchedule, Date timestamp); + public List getVolumeObjectTos() { + return volumeObjectTos; + } } diff --git a/core/src/main/java/com/cloud/agent/api/storage/RevertDiskOnlyVmSnapshotCommand.java b/core/src/main/java/com/cloud/agent/api/storage/RevertDiskOnlyVmSnapshotCommand.java new file mode 100644 index 000000000000..72bb92bcb10d --- /dev/null +++ b/core/src/main/java/com/cloud/agent/api/storage/RevertDiskOnlyVmSnapshotCommand.java @@ -0,0 +1,50 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package com.cloud.agent.api.storage; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.SnapshotObjectTO; + +import java.util.List; + +public class RevertDiskOnlyVmSnapshotCommand extends Command { + + private List snapshotObjectTos; + private String vmName; + + public RevertDiskOnlyVmSnapshotCommand(List snapshotObjectTos, String vmName) { + super(); + this.snapshotObjectTos = snapshotObjectTos; + this.vmName = vmName; + } + + public List getSnapshotObjectTos() { + return snapshotObjectTos; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java index f9ea3e05e97f..7bfbf786e9b4 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VRScripts.java @@ -82,5 +82,8 @@ public class VRScripts { public static final String VR_UPDATE_INTERFACE_CONFIG = "update_interface_config.sh"; public static final String ROUTER_FILESYSTEM_WRITABLE_CHECK = "filesystem_writable_check.py"; + + // CKS ISO mount + public static final String CKS_ISO_MOUNT_SERVE = "cks_iso.sh"; public static final String MANAGE_SERVICE = "manage_service.sh"; } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java index fb04193271ee..b9ac455130f3 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResource.java @@ -34,6 +34,7 @@ import javax.naming.ConfigurationException; +import com.cloud.agent.api.HandleCksIsoCommand; import org.apache.cloudstack.agent.routing.ManageServiceCommand; import com.cloud.agent.api.routing.UpdateNetworkCommand; import com.cloud.agent.api.to.IpAddressTO; @@ -145,6 +146,10 @@ public Answer executeRequest(final NetworkElementCommand cmd) { return execute((UpdateNetworkCommand) cmd); } + if (cmd instanceof HandleCksIsoCommand) { + return execute((HandleCksIsoCommand) cmd); + } + if (cmd instanceof ManageServiceCommand) { return execute((ManageServiceCommand) cmd); } @@ -176,6 +181,13 @@ public Answer executeRequest(final NetworkElementCommand cmd) { } } + protected Answer execute(final HandleCksIsoCommand cmd) { + String routerIp = getRouterSshControlIp(cmd); + logger.info("Attempting to mount CKS ISO on Virtual Router"); + ExecutionResult result = _vrDeployer.executeInVR(routerIp, VRScripts.CKS_ISO_MOUNT_SERVE, String.valueOf(cmd.isMountCksIso())); + return new Answer(cmd, result.isSuccess(), result.getDetails()); + } + private Answer execute(final SetupKeyStoreCommand cmd) { final String args = String.format("/usr/local/cloud/systemvm/conf/agent.properties " + "/usr/local/cloud/systemvm/conf/%s " + diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java index 9c5b657bb4e4..041645046b23 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/DhcpEntryConfigItem.java @@ -35,7 +35,7 @@ public List generateConfig(final NetworkElementCommand cmd) { final DhcpEntryCommand command = (DhcpEntryCommand) cmd; final VmDhcpConfig vmDhcpConfig = new VmDhcpConfig(command.getVmName(), command.getVmMac(), command.getVmIpAddress(), command.getVmIp6Address(), command.getDuid(), command.getDefaultDns(), - command.getDefaultRouter(), command.getStaticRoutes(), command.isDefault(), command.isRemove()); + command.getDefaultRouter(), command.getStaticRoutes(), command.isDefault(), command.isRemove(), command.getLeaseTime()); return generateConfigItems(vmDhcpConfig); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java index 4832c906699e..6dae886b4137 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/LoadBalancerConfigItem.java @@ -56,6 +56,8 @@ public List generateConfig(final NetworkElementCommand cmd) { final String[] statRules = allRules[LoadBalancerConfigurator.STATS]; final LoadBalancerRule loadBalancerRule = new LoadBalancerRule(configuration, tmpCfgFilePath, tmpCfgFileName, addRules, removeRules, statRules, routerIp); + final LoadBalancerRule.SslCertEntry[] sslCerts = cfgtr.generateSslCertEntries(command); + loadBalancerRule.setSslCerts(sslCerts); final List rules = new LinkedList(); rules.add(loadBalancerRule); diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java index 1a6824ceb7fc..57c96ec4bc95 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/facade/SetGuestNetworkConfigItem.java @@ -76,6 +76,7 @@ public List generateConfig(final NetworkElementCommand cmd) { guestNetwork.setRouterIp6Gateway(command.getRouterIpv6Gateway()); guestNetwork.setRouterIp6Cidr(command.getRouterIpv6Cidr()); guestNetwork.setVrGuestGateway(command.isVrGuestGateway()); + guestNetwork.setNetworkId(command.getNetworkId()); return generateConfigItems(guestNetwork); } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java index a416b4bc5e41..6bf36d62bf9f 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/GuestNetwork.java @@ -38,6 +38,7 @@ public class GuestNetwork extends ConfigBase { private String routerIp6Gateway; private String routerIp6Cidr; private boolean isVrGuestGateway; + long networkId; private Integer mtu; @@ -211,4 +212,12 @@ public boolean isVrGuestGateway() { public void setVrGuestGateway(boolean vrGuestGateway) { isVrGuestGateway = vrGuestGateway; } + + public long getNetworkId() { + return networkId; + } + + public void setNetworkId(long networkId) { + this.networkId = networkId; + } } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java index 3743d608e6c2..361c4765cc52 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRule.java @@ -25,6 +25,7 @@ public class LoadBalancerRule { private String[] configuration; private String tmpCfgFilePath; private String tmpCfgFileName; + private SslCertEntry[] sslCerts; private String[] addRules; private String[] removeRules; @@ -32,6 +33,53 @@ public class LoadBalancerRule { private String routerIp; + public static class SslCertEntry { + private String name; + private String cert; + private String key; + private String chain; + private String password; + + public SslCertEntry(String name, String cert, String key, String chain, String password) { + this.name = name; + this.cert = cert; + this.key = key; + this.chain = chain; + this.password = password; + } + + public void setName(String name) { + this.name = name; + } + public String getName() { + return name; + } + public void setCert(String cert) { + this.cert = cert; + } + public String getCert() { + return cert; + } + public void setKey(String key) { + this.key = key; + } + public String getKey() { + return key; + } + public void setChain(String chain) { + this.chain = chain; + } + public String getChain() { + return chain; + } + public void setPassword(String password) { + this.password = password; + } + public String getPassword() { + return password; + } + } + public LoadBalancerRule() { // Empty constructor for (de)serialization } @@ -101,4 +149,12 @@ public String getRouterIp() { public void setRouterIp(final String routerIp) { this.routerIp = routerIp; } + + public SslCertEntry[] getSslCerts() { + return sslCerts; + } + + public void setSslCerts(final SslCertEntry[] sslCerts) { + this.sslCerts = sslCerts; + } } diff --git a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java index d9cb8b0b2645..1c43cd1823b9 100644 --- a/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java +++ b/core/src/main/java/com/cloud/agent/resource/virtualnetwork/model/VmDhcpConfig.java @@ -29,6 +29,7 @@ public class VmDhcpConfig extends ConfigBase { private String defaultGateway; private String staticRoutes; private boolean defaultEntry; + private Long leaseTime; // Indicate if the entry should be removed when set to true private boolean remove; @@ -39,6 +40,11 @@ public VmDhcpConfig() { public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, String ipv6Address, String ipv6Duid, String dnsAddresses, String defaultGateway, String staticRoutes, boolean defaultEntry, boolean remove) { + this(hostName, macAddress, ipv4Address, ipv6Address, ipv6Duid, dnsAddresses, defaultGateway, staticRoutes, defaultEntry, remove, null); + } + + public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, String ipv6Address, String ipv6Duid, String dnsAddresses, String defaultGateway, + String staticRoutes, boolean defaultEntry, boolean remove, Long leaseTime) { super(VM_DHCP); this.hostName = hostName; this.macAddress = macAddress; @@ -50,6 +56,7 @@ public VmDhcpConfig(String hostName, String macAddress, String ipv4Address, Stri this.staticRoutes = staticRoutes; this.defaultEntry = defaultEntry; this.remove = remove; + this.leaseTime = leaseTime; } public String getHostName() { @@ -132,4 +139,12 @@ public void setDefaultEntry(boolean defaultEntry) { this.defaultEntry = defaultEntry; } + public Long getLeaseTime() { + return leaseTime; + } + + public void setLeaseTime(Long leaseTime) { + this.leaseTime = leaseTime; + } + } diff --git a/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java b/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java index 7229f0903b74..eabcc82040d6 100644 --- a/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java +++ b/core/src/main/java/com/cloud/agent/transport/ArrayTypeAdaptor.java @@ -75,13 +75,17 @@ public T[] deserialize(JsonElement json, Type typeOfT, JsonDeserializationContex try { clazz = Class.forName(name); } catch (ClassNotFoundException e) { - throw new CloudRuntimeException("can't find " + name); + throw new JsonParseException("can't find " + name); } T cmd = (T)_gson.fromJson(entry.getValue(), clazz); cmds.add(cmd); } - Class type = ((Class)typeOfT).getComponentType(); - T[] ts = (T[])Array.newInstance(type, cmds.size()); - return cmds.toArray(ts); + try { + Class type = Class.forName(typeOfT.getTypeName().replace("[]", "")); + T[] ts = (T[])Array.newInstance(type, cmds.size()); + return cmds.toArray(ts); + } catch (ClassNotFoundException e) { + throw new CloudRuntimeException("can't find " + typeOfT.getTypeName()); + } } } diff --git a/core/src/main/java/com/cloud/network/HAProxyConfigurator.java b/core/src/main/java/com/cloud/network/HAProxyConfigurator.java index e4b0a7ffff4c..7d544c2e49c0 100644 --- a/core/src/main/java/com/cloud/network/HAProxyConfigurator.java +++ b/core/src/main/java/com/cloud/network/HAProxyConfigurator.java @@ -36,6 +36,8 @@ import com.cloud.agent.api.to.LoadBalancerTO.DestinationTO; import com.cloud.agent.api.to.LoadBalancerTO.StickinessPolicyTO; import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRule.SslCertEntry; +import com.cloud.network.lb.LoadBalancingRule.LbSslCert; import com.cloud.network.rules.LbStickinessMethod.StickinessMethodType; import com.cloud.utils.Pair; import com.cloud.utils.net.NetUtils; @@ -52,6 +54,12 @@ public class HAProxyConfigurator implements LoadBalancerConfigurator { private static String[] defaultListen = {"listen vmops", "\tbind 0.0.0.0:9", "\toption transparent"}; + private static final String SSL_CERTS_DIR = "/etc/cloudstack/ssl/"; + + private static final String SSL_CONFIGURATION_INTERMEDIATE = " ssl-min-ver TLSv1.2 no-tls-tickets " + + "ciphers ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-GCM-SHA256 " + + "ciphersuites TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256"; + @Override public String[] generateConfiguration(final List fwRules) { // Group the rules by publicip:publicport @@ -469,30 +477,41 @@ private String getLbSubRuleForStickiness(final LoadBalancerTO lbTO) { return sb.toString(); } - private List getRulesForPool(final LoadBalancerTO lbTO, final boolean keepAliveEnabled) { + private List getRulesForPool(final LoadBalancerTO lbTO, final LoadBalancerConfigCommand lbCmd) { StringBuilder sb = new StringBuilder(); final String poolName = sb.append(lbTO.getSrcIp().replace(".", "_")).append('-').append(lbTO.getSrcPort()).toString(); final String publicIP = lbTO.getSrcIp(); final int publicPort = lbTO.getSrcPort(); final String algorithm = lbTO.getAlgorithm(); - final List result = new ArrayList(); - // add line like this: "listen 65_37_141_30-80\n\tbind 65.37.141.30:80" - sb = new StringBuilder(); - sb.append("listen ").append(poolName); - result.add(sb.toString()); + boolean sslOffloading = lbTO.getSslCert() != null && !lbTO.getSslCert().isRevoked() + && NetUtils.SSL_PROTO.equals(lbTO.getLbProtocol()); + + final List frontendConfigs = new ArrayList<>(); + final List backendConfigs = new ArrayList<>(); + final List result = new ArrayList<>(); + sb = new StringBuilder(); sb.append("\tbind ").append(publicIP).append(":").append(publicPort); - result.add(sb.toString()); + + if (sslOffloading) { + sb.append(" ssl crt ").append(SSL_CERTS_DIR).append(poolName).append(".pem"); + // check for http2 support + sb.append(" alpn h2,http/1.1"); + sb.append(SSL_CONFIGURATION_INTERMEDIATE); + sb.append("\n\thttp-request add-header X-Forwarded-Proto https"); + } + frontendConfigs.add(sb.toString()); + sb = new StringBuilder(); sb.append("\t").append("balance ").append(algorithm.toLowerCase()); - result.add(sb.toString()); + backendConfigs.add(sb.toString()); int i = 0; - Boolean destsAvailable = false; + boolean destsAvailable = false; final String stickinessSubRule = getLbSubRuleForStickiness(lbTO); - final List dstSubRule = new ArrayList(); - final List dstWithCookieSubRule = new ArrayList(); + final List dstSubRule = new ArrayList<>(); + final List dstWithCookieSubRule = new ArrayList<>(); for (final DestinationTO dest : lbTO.getDestinations()) { // add line like this: "server 65_37_141_30-80_3 10.1.1.4:80 check" if (dest.isRevoked()) { @@ -500,15 +519,20 @@ private List getRulesForPool(final LoadBalancerTO lbTO, final boolean ke } sb = new StringBuilder(); sb.append("\t") - .append("server ") - .append(poolName) - .append("_") - .append(Integer.toString(i++)) - .append(" ") - .append(dest.getDestIp()) - .append(":") - .append(dest.getDestPort()) - .append(" check"); + .append("server ") + .append(poolName) + .append("_") + .append(i++) + .append(" ") + .append(dest.getDestIp()) + .append(":") + .append(dest.getDestPort()) + .append(" check"); + + if (sslOffloading) { + sb.append(SSL_CONFIGURATION_INTERMEDIATE); + } + if(lbTO.getLbProtocol() != null && lbTO.getLbProtocol().equals("tcp-proxy")) { sb.append(" send-proxy"); } @@ -520,9 +544,9 @@ private List getRulesForPool(final LoadBalancerTO lbTO, final boolean ke destsAvailable = true; } - Boolean httpbasedStickiness = false; + boolean httpbasedStickiness = false; /* attach stickiness sub rule only if the destinations are available */ - if (stickinessSubRule != null && destsAvailable == true) { + if (stickinessSubRule != null && destsAvailable) { for (final StickinessPolicyTO stickinessPolicy : lbTO.getStickinessPolicies()) { if (stickinessPolicy == null) { continue; @@ -530,35 +554,40 @@ private List getRulesForPool(final LoadBalancerTO lbTO, final boolean ke if (StickinessMethodType.LBCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName()) || StickinessMethodType.AppCookieBased.getName().equalsIgnoreCase(stickinessPolicy.getMethodName())) { httpbasedStickiness = true; + break; } } if (httpbasedStickiness) { - result.addAll(dstWithCookieSubRule); + backendConfigs.addAll(dstWithCookieSubRule); } else { - result.addAll(dstSubRule); + backendConfigs.addAll(dstSubRule); } - result.add(stickinessSubRule); + backendConfigs.add(stickinessSubRule); } else { - result.addAll(dstSubRule); + backendConfigs.addAll(dstSubRule); } if (stickinessSubRule != null && !destsAvailable) { logger.warn("Haproxy stickiness policy for lb rule: " + lbTO.getSrcIp() + ":" + lbTO.getSrcPort() + ": Not Applied, cause: backends are unavailable"); } - if (publicPort == NetUtils.HTTP_PORT && !keepAliveEnabled || httpbasedStickiness) { - sb = new StringBuilder(); - sb.append("\t").append("mode http"); - result.add(sb.toString()); - sb = new StringBuilder(); - sb.append("\t").append("option httpclose"); - result.add(sb.toString()); + boolean keepAliveEnabled = lbCmd.keepAliveEnabled; + boolean http = (publicPort == NetUtils.HTTP_PORT && !keepAliveEnabled); + if (http || httpbasedStickiness || sslOffloading) { + frontendConfigs.add("\tmode http"); + String keepAliveLine = keepAliveEnabled ? "\tno option forceclose" : "\toption httpclose"; + frontendConfigs.add(keepAliveLine); } + // add line like this: "listen 65_37_141_30-80\n\tbind 65.37.141.30:80" + result.add(String.format("listen %s", poolName)); + result.addAll(frontendConfigs); + String cidrList = lbTO.getCidrList(); if (StringUtils.isNotBlank(cidrList)) { result.add(String.format("\tacl network_allowed src %s \n\ttcp-request connection reject if !network_allowed", cidrList)); } + result.addAll(backendConfigs); result.add(blankLine); return result; } @@ -566,15 +595,18 @@ private List getRulesForPool(final LoadBalancerTO lbTO, final boolean ke private String generateStatsRule(final LoadBalancerConfigCommand lbCmd, final String ruleName, final String statsIp) { final StringBuilder rule = new StringBuilder("\nlisten ").append(ruleName).append("\n\tbind ").append(statsIp).append(":").append(lbCmd.lbStatsPort); // TODO DH: write test for this in both cases - if (!lbCmd.keepAliveEnabled) { - logger.info("Haproxy mode http enabled"); - rule.append("\n\tmode http\n\toption httpclose"); + rule.append("\n\tmode http"); + if (lbCmd.keepAliveEnabled) { + logger.info("Haproxy option http-keep-alive enabled"); + } else { + logger.info("Haproxy option httpclose enabled"); + rule.append("\n\toption httpclose"); } rule.append("\n\tstats enable\n\tstats uri ") - .append(lbCmd.lbStatsUri) - .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") - .append(lbCmd.lbStatsAuth); - rule.append("\n"); + .append(lbCmd.lbStatsUri) + .append("\n\tstats realm Haproxy\\ Statistics\n\tstats auth ") + .append(lbCmd.lbStatsAuth) + .append("\n"); final String result = rule.toString(); if (logger.isDebugEnabled()) { logger.debug("Haproxystats rule: " + result); @@ -597,15 +629,25 @@ public String[] generateConfiguration(final LoadBalancerConfigCommand lbCmd) { } } result.addAll(gSection); - // TODO decide under what circumstances these options are needed - // result.add("\tnokqueue"); - // result.add("\tnopoll"); result.add(blankLine); final List dSection = Arrays.asList(defaultsSection); if (lbCmd.keepAliveEnabled) { dSection.set(7, "\tno option httpclose"); } + if (lbCmd.idleTimeout > 0) { + dSection.set(9, "\ttimeout client " + Long.toString(lbCmd.idleTimeout)); + dSection.set(10, "\ttimeout server " + Long.toString(lbCmd.idleTimeout)); + } else if (lbCmd.idleTimeout == 0) { + // .remove() is not allowed, only .set() operations are allowed as the list + // is a fixed size. So lets just mark the entry as blank. + dSection.set(9, ""); + dSection.set(10, ""); + } else { + // Negative idleTimeout values are considered invalid; retain the + // default HAProxy timeout values from defaultsSection for predictability. + logger.warn("Negative idleTimeout ({}) configured; retaining default HAProxy timeouts.", lbCmd.idleTimeout); + } if (logger.isDebugEnabled()) { for (final String s : dSection) { @@ -644,7 +686,7 @@ public String[] generateConfiguration(final LoadBalancerConfigCommand lbCmd) { if (lbTO.isRevoked()) { continue; } - final List poolRules = getRulesForPool(lbTO, lbCmd.keepAliveEnabled); + final List poolRules = getRulesForPool(lbTO, lbCmd); result.addAll(poolRules); has_listener = true; } @@ -696,4 +738,30 @@ public String[][] generateFwRules(final LoadBalancerConfigCommand lbCmd) { return result; } + + @Override + public SslCertEntry[] generateSslCertEntries(LoadBalancerConfigCommand lbCmd) { + final Set sslCertEntries = new HashSet<>(); + for (final LoadBalancerTO lbTO : lbCmd.getLoadBalancers()) { + if (lbTO.getSslCert() != null) { + addSslCertEntry(sslCertEntries, lbTO); + } + } + final SslCertEntry[] result = sslCertEntries.toArray(new SslCertEntry[sslCertEntries.size()]); + return result; + } + + private void addSslCertEntry(Set sslCertEntries, LoadBalancerTO lbTO) { + final LbSslCert cert = lbTO.getSslCert(); + if (cert.isRevoked()) { + return; + } + if (lbTO.getLbProtocol() == null || ! lbTO.getLbProtocol().equals(NetUtils.SSL_PROTO)) { + return; + } + StringBuilder sb = new StringBuilder(); + final String name = sb.append(lbTO.getSrcIp().replace(".", "_")).append('-').append(lbTO.getSrcPort()).toString(); + final SslCertEntry sslCertEntry = new SslCertEntry(name, cert.getCert(), cert.getKey(), cert.getChain(), cert.getPassword()); + sslCertEntries.add(sslCertEntry); + } } diff --git a/core/src/main/java/com/cloud/network/LoadBalancerConfigurator.java b/core/src/main/java/com/cloud/network/LoadBalancerConfigurator.java index 0e19b1e606e9..8814f60b0714 100644 --- a/core/src/main/java/com/cloud/network/LoadBalancerConfigurator.java +++ b/core/src/main/java/com/cloud/network/LoadBalancerConfigurator.java @@ -23,6 +23,7 @@ import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.to.PortForwardingRuleTO; +import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRule.SslCertEntry; public interface LoadBalancerConfigurator { public final static int ADD = 0; @@ -34,4 +35,6 @@ public interface LoadBalancerConfigurator { public String[] generateConfiguration(LoadBalancerConfigCommand lbCmd); public String[][] generateFwRules(LoadBalancerConfigCommand lbCmd); + + public SslCertEntry[] generateSslCertEntries(LoadBalancerConfigCommand lbCmd); } diff --git a/core/src/main/java/com/cloud/resource/ServerResource.java b/core/src/main/java/com/cloud/resource/ServerResource.java index 092019e7f21b..23d200942a27 100644 --- a/core/src/main/java/com/cloud/resource/ServerResource.java +++ b/core/src/main/java/com/cloud/resource/ServerResource.java @@ -22,6 +22,7 @@ import com.cloud.agent.IAgentControl; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; +import com.cloud.agent.api.PingAnswer; import com.cloud.agent.api.PingCommand; import com.cloud.agent.api.StartupCommand; import com.cloud.host.Host; @@ -50,6 +51,10 @@ public interface ServerResource extends Manager { */ StartupCommand[] initialize(); + default StartupCommand[] initialize(boolean isTransferredConnection) { + return initialize(); + } + /** * @param id id of the server to put in the PingCommand * @return PingCommand @@ -86,4 +91,5 @@ default boolean isAppendAgentNameToLogs() { return false; } + default void processPingAnswer(PingAnswer answer) {}; } diff --git a/core/src/main/java/com/cloud/serializer/GsonHelper.java b/core/src/main/java/com/cloud/serializer/GsonHelper.java index 2d2cecf26185..7de98c08b7e0 100644 --- a/core/src/main/java/com/cloud/serializer/GsonHelper.java +++ b/core/src/main/java/com/cloud/serializer/GsonHelper.java @@ -62,7 +62,7 @@ public class GsonHelper { LOGGER.info("Default Builder inited."); } - static Gson setDefaultGsonConfig(GsonBuilder builder) { + public static Gson setDefaultGsonConfig(GsonBuilder builder) { builder.setVersion(1.5); InterfaceTypeAdaptor dsAdaptor = new InterfaceTypeAdaptor(); builder.registerTypeAdapter(DataStoreTO.class, dsAdaptor); diff --git a/core/src/main/java/com/cloud/storage/resource/StorageProcessor.java b/core/src/main/java/com/cloud/storage/resource/StorageProcessor.java index b7cd5b8eb5ee..31c384eab3a0 100644 --- a/core/src/main/java/com/cloud/storage/resource/StorageProcessor.java +++ b/core/src/main/java/com/cloud/storage/resource/StorageProcessor.java @@ -21,7 +21,7 @@ import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.storage.command.AttachCommand; -import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand; +import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplianceCommand; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.CreateObjectCommand; import org.apache.cloudstack.storage.command.DeleteCommand; @@ -82,7 +82,11 @@ public interface StorageProcessor { Answer copyVolumeFromPrimaryToPrimary(CopyCommand cmd); - public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplainceCommand cmd); + public Answer checkDataStoreStoragePolicyCompliance(CheckDataStoreStoragePolicyComplianceCommand cmd); public Answer syncVolumePath(SyncVolumePathCommand cmd); + + default Answer deleteBackup(DeleteCommand cmd) { + return new Answer(cmd, false, "Operation not implemented"); + } } diff --git a/core/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java b/core/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java index 7d8225462cab..3d2608c0c4b8 100644 --- a/core/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java +++ b/core/src/main/java/com/cloud/storage/resource/StorageSubsystemCommandHandlerBase.java @@ -21,7 +21,7 @@ import org.apache.cloudstack.agent.directdownload.DirectDownloadCommand; import org.apache.cloudstack.storage.command.AttachCommand; -import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplainceCommand; +import org.apache.cloudstack.storage.command.CheckDataStoreStoragePolicyComplianceCommand; import org.apache.cloudstack.storage.command.CopyCommand; import org.apache.cloudstack.storage.command.CreateObjectAnswer; import org.apache.cloudstack.storage.command.CreateObjectCommand; @@ -79,8 +79,8 @@ public Answer handleStorageCommands(StorageSubSystemCommand command) { return processor.resignature((ResignatureCommand) command); } else if (command instanceof DirectDownloadCommand) { return processor.handleDownloadTemplateToPrimaryStorage((DirectDownloadCommand) command); - } else if (command instanceof CheckDataStoreStoragePolicyComplainceCommand) { - return processor.checkDataStoreStoragePolicyCompliance((CheckDataStoreStoragePolicyComplainceCommand) command); + } else if (command instanceof CheckDataStoreStoragePolicyComplianceCommand) { + return processor.checkDataStoreStoragePolicyCompliance((CheckDataStoreStoragePolicyComplianceCommand) command); } else if (command instanceof SyncVolumePathCommand) { return processor.syncVolumePath((SyncVolumePathCommand) command); } else if (command instanceof QuerySnapshotZoneCopyCommand) { @@ -142,7 +142,7 @@ protected Answer execute(CreateObjectCommand cmd) { } return new CreateObjectAnswer("not supported type"); } catch (Exception e) { - logger.debug("Failed to create object: " + data.getObjectType() + ": " + e.toString()); + logger.error("Failed to create object [{}] due to [{}].", data.getObjectType(), e.getMessage(), e); return new CreateObjectAnswer(e.toString()); } } @@ -154,6 +154,8 @@ protected Answer execute(DeleteCommand cmd) { answer = processor.deleteVolume(cmd); } else if (data.getObjectType() == DataObjectType.SNAPSHOT) { answer = processor.deleteSnapshot(cmd); + } else if (data.getObjectType() == DataObjectType.BACKUP) { + answer = processor.deleteBackup(cmd); } else { answer = new Answer(cmd, false, "unsupported type"); } diff --git a/core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java b/core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java index 6fe001de72c0..71c329796d1b 100755 --- a/core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java +++ b/core/src/main/java/com/cloud/storage/template/HttpTemplateDownloader.java @@ -52,6 +52,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.UriUtils; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.HttpClientCloudStackUserAgent; import com.cloud.utils.net.Proxy; /** @@ -125,6 +126,7 @@ private GetMethod createRequest(String downloadUrl) { GetMethod request = new GetMethod(downloadUrl); request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler); request.setFollowRedirects(followRedirects); + request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT); return request; } diff --git a/core/src/main/java/com/cloud/storage/template/MetalinkTemplateDownloader.java b/core/src/main/java/com/cloud/storage/template/MetalinkTemplateDownloader.java index 95ed0d1e76d0..0dad2564779c 100644 --- a/core/src/main/java/com/cloud/storage/template/MetalinkTemplateDownloader.java +++ b/core/src/main/java/com/cloud/storage/template/MetalinkTemplateDownloader.java @@ -18,8 +18,11 @@ // package com.cloud.storage.template; + import com.cloud.storage.StorageLayer; import com.cloud.utils.UriUtils; +import com.cloud.utils.net.HttpClientCloudStackUserAgent; + import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpMethod; import org.apache.commons.httpclient.HttpMethodRetryHandler; @@ -59,6 +62,7 @@ protected GetMethod createRequest(String downloadUrl) { GetMethod request = new GetMethod(downloadUrl); request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, myretryhandler); request.setFollowRedirects(followRedirects); + request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT); if (!toFileSet) { String[] parts = downloadUrl.split("/"); String filename = parts[parts.length - 1]; diff --git a/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java b/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java index 8719947cb4f0..6608754073a1 100644 --- a/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java +++ b/core/src/main/java/com/cloud/storage/template/SimpleHttpMultiFileDownloader.java @@ -44,6 +44,7 @@ import org.apache.commons.lang3.StringUtils; import com.cloud.storage.StorageLayer; +import com.cloud.utils.net.HttpClientCloudStackUserAgent; public class SimpleHttpMultiFileDownloader extends ManagedContextRunnable implements TemplateDownloader { private static final MultiThreadedHttpConnectionManager s_httpClientManager = new MultiThreadedHttpConnectionManager(); @@ -95,6 +96,7 @@ private GetMethod createRequest(String downloadUrl) { GetMethod request = new GetMethod(downloadUrl); request.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, retryHandler); request.setFollowRedirects(followRedirects); + request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT); return request; } @@ -141,6 +143,7 @@ private void tryAndGetTotalRemoteSize() { continue; } HeadMethod headMethod = new HeadMethod(downloadUrl); + headMethod.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT); try { if (client.executeMethod(headMethod) != HttpStatus.SC_OK) { continue; diff --git a/core/src/main/java/com/cloud/storage/template/TemplateConstants.java b/core/src/main/java/com/cloud/storage/template/TemplateConstants.java index d6622bed73ef..349997da1b7a 100644 --- a/core/src/main/java/com/cloud/storage/template/TemplateConstants.java +++ b/core/src/main/java/com/cloud/storage/template/TemplateConstants.java @@ -24,7 +24,7 @@ public final class TemplateConstants { public static final String DEFAULT_SNAPSHOT_ROOT_DIR = "snapshots"; public static final String DEFAULT_VOLUME_ROOT_DIR = "volumes"; public static final String DEFAULT_TMPLT_FIRST_LEVEL_DIR = "tmpl/"; - + public static final String DEFAULT_CHECKPOINT_ROOT_DIR = "checkpoints"; public static final String DEFAULT_SYSTEM_VM_TEMPLATE_PATH = "template/tmpl/1/"; public static final int DEFAULT_TMPLT_COPY_PORT = 80; diff --git a/core/src/main/java/org/apache/cloudstack/agent/lb/SetupMSListCommand.java b/core/src/main/java/org/apache/cloudstack/agent/lb/SetupMSListCommand.java index 50cf956c9e75..864a3e22eb3e 100644 --- a/core/src/main/java/org/apache/cloudstack/agent/lb/SetupMSListCommand.java +++ b/core/src/main/java/org/apache/cloudstack/agent/lb/SetupMSListCommand.java @@ -26,20 +26,28 @@ public class SetupMSListCommand extends Command { private List msList; + private List avoidMsList; private String lbAlgorithm; private Long lbCheckInterval; + private Boolean triggerHostLb; - public SetupMSListCommand(final List msList, final String lbAlgorithm, final Long lbCheckInterval) { + public SetupMSListCommand(final List msList, final List avoidMsList, final String lbAlgorithm, final Long lbCheckInterval, final Boolean triggerHostLb) { super(); this.msList = msList; + this.avoidMsList = avoidMsList; this.lbAlgorithm = lbAlgorithm; this.lbCheckInterval = lbCheckInterval; + this.triggerHostLb = triggerHostLb; } public List getMsList() { return msList; } + public List getAvoidMsList() { + return avoidMsList; + } + public String getLbAlgorithm() { return lbAlgorithm; } @@ -48,9 +56,12 @@ public Long getLbCheckInterval() { return lbCheckInterval; } + public boolean getTriggerHostLb() { + return triggerHostLb; + } + @Override public boolean executeInSequence() { return false; } - } diff --git a/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java index 09f9c5621502..abe78ee5553d 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java +++ b/core/src/main/java/org/apache/cloudstack/backup/BackupAnswer.java @@ -28,6 +28,13 @@ public class BackupAnswer extends Answer { private Long size; private Long virtualSize; private Map volumes; + Boolean needsCleanup; + // Set by the NAS backup provider after a checkpoint/bitmap was created during this backup. + // The provider persists it in backup_details under NASBackupChainKeys.BITMAP_NAME. + private String bitmapCreated; + // Set when an incremental was requested but the agent had to fall back to a full + // (e.g. VM was stopped). Provider should record this backup as type=full. + private Boolean incrementalFallback; public BackupAnswer(final Command command, final boolean success, final String details) { super(command, success, details); @@ -56,4 +63,32 @@ public Map getVolumes() { public void setVolumes(Map volumes) { this.volumes = volumes; } + + public Boolean getNeedsCleanup() { + if (needsCleanup == null) { + return false; + } + return needsCleanup; + } + + public void setNeedsCleanup(Boolean needsCleanup) { + this.needsCleanup = needsCleanup; + } + + public String getBitmapCreated() { + return bitmapCreated; + } + + public void setBitmapCreated(String bitmapCreated) { + this.bitmapCreated = bitmapCreated; + } + + public Boolean getIncrementalFallback() { + return incrementalFallback != null && incrementalFallback; + } + + public void setIncrementalFallback(Boolean incrementalFallback) { + this.incrementalFallback = incrementalFallback; + } + } diff --git a/core/src/main/java/org/apache/cloudstack/backup/BackupStorageStatsAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/BackupStorageStatsAnswer.java new file mode 100644 index 000000000000..eabf6877ba6e --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/BackupStorageStatsAnswer.java @@ -0,0 +1,50 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +public class BackupStorageStatsAnswer extends Answer { + private Long totalSize; + private Long usedSize; + + public BackupStorageStatsAnswer(final Command command, final boolean success, final String details) { + super(command, success, details); + this.totalSize = 0L; + this.usedSize = 0L; + } + + public Long getTotalSize() { + return totalSize; + } + + public void setTotalSize(Long totalSize) { + this.totalSize = totalSize; + } + + public Long getUsedSize() { + return usedSize; + } + + public void setUsedSize(Long usedSize) { + this.usedSize = usedSize; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossBackupErrorAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossBackupErrorAnswer.java new file mode 100644 index 000000000000..042047b59358 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossBackupErrorAnswer.java @@ -0,0 +1,46 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.backup; + +import java.util.Map; + +import org.apache.commons.collections4.MapUtils; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.utils.Pair; + +public class CleanupKbossBackupErrorAnswer extends Answer { + private Map> volumeIdToPathAndChainEnded; + private boolean vmRunning; + + public CleanupKbossBackupErrorAnswer(Command cmd, Map> volumeIdToPathAndChainEnded, boolean vmRunning) { + super(cmd, MapUtils.isNotEmpty(volumeIdToPathAndChainEnded), null); + this.volumeIdToPathAndChainEnded = volumeIdToPathAndChainEnded; + this.vmRunning = vmRunning; + } + + public Map> getVolumeIdToPathAndChainEnded() { + return volumeIdToPathAndChainEnded; + } + + public boolean isVmRunning() { + return vmRunning; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossBackupErrorCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossBackupErrorCommand.java new file mode 100644 index 000000000000..e5cd5a7f8150 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossBackupErrorCommand.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.KbossTO; + +import java.util.List; + +public class CleanupKbossBackupErrorCommand extends Command { + + private boolean runningVM; + + private boolean errorOnCreate; + + private boolean endOfChain; + + private boolean isTopDelta; + + private String vmName; + + private String imageStoreUrl; + + private List kbossTOS; + + public CleanupKbossBackupErrorCommand(boolean runningVM, boolean errorOnCreate, boolean endOfChain, boolean isTopDelta, String vmName, String imageStoreUrl, List kbossTOS) { + this.errorOnCreate = errorOnCreate; + this.runningVM = runningVM; + this.endOfChain = endOfChain; + this.isTopDelta = isTopDelta; + this.vmName = vmName; + this.imageStoreUrl = imageStoreUrl; + this.kbossTOS = kbossTOS; + } + + public boolean isErrorOnCreate() { + return errorOnCreate; + } + + public boolean isEndOfChain() { + return endOfChain; + } + + public boolean isTopDelta() { + return isTopDelta; + } + + public boolean isRunningVM() { + return runningVM; + } + + public String getVmName() { + return vmName; + } + + public String getImageStoreUrl() { + return imageStoreUrl; + } + + public List getKbossTOs() { + return kbossTOS; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossValidationCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossValidationCommand.java new file mode 100644 index 000000000000..8a345aeba176 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CleanupKbossValidationCommand.java @@ -0,0 +1,49 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +import java.util.Set; + +public class CleanupKbossValidationCommand extends Command { + + private String vmName; + + private Set secondaryStorages; + + public CleanupKbossValidationCommand(String vmName, Set secondaryStorages) { + this.vmName = vmName; + this.secondaryStorages = secondaryStorages; + } + + public String getVmName() { + return vmName; + } + + public Set getSecondaryStorages() { + return secondaryStorages; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CompressBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CompressBackupCommand.java new file mode 100644 index 000000000000..a551f246a0bb --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CompressBackupCommand.java @@ -0,0 +1,78 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.DeltaMergeTreeTO; + +import java.util.List; + +public class CompressBackupCommand extends Command { + + private List backupDeltasToCompress; + + private List backupChainImageStoreUrls; + + private long minFreeStorage; + + private Backup.CompressionLibrary compressionLib; + + private int coroutines; + + private int rateLimit; + + public CompressBackupCommand(List backupDeltasToCompress, List backupChainImageStoreUrls, long minFreeStorage, Backup.CompressionLibrary compressionLib, int coroutines, int rateLimit) { + this.backupChainImageStoreUrls = backupChainImageStoreUrls; + this.backupDeltasToCompress = backupDeltasToCompress; + this.minFreeStorage = minFreeStorage; + this.compressionLib = compressionLib; + this.coroutines = coroutines; + this.rateLimit = rateLimit; + } + + public List getBackupDeltasToCompress() { + return backupDeltasToCompress; + } + + public List getBackupChainImageStoreUrls() { + return backupChainImageStoreUrls; + } + + public long getMinFreeStorage() { + return minFreeStorage; + } + + public Backup.CompressionLibrary getCompressionLib() { + return compressionLib; + } + + public int getCoroutines() { + return coroutines; + } + + public int getRateLimit() { + return rateLimit; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/ConsolidateVolumesAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/ConsolidateVolumesAnswer.java new file mode 100644 index 000000000000..9f230deb8d75 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/ConsolidateVolumesAnswer.java @@ -0,0 +1,37 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import java.util.List; + +public class ConsolidateVolumesAnswer extends Answer { + + private List successfullyConsolidatedVolumes; + + public ConsolidateVolumesAnswer(Command command, boolean success, String details, List successfullyConsolidatedVolumes) { + super(command, success, details); + this.successfullyConsolidatedVolumes = successfullyConsolidatedVolumes; + } + + public List getSuccessfullyConsolidatedVolumes() { + return successfullyConsolidatedVolumes; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/ConsolidateVolumesCommand.java b/core/src/main/java/org/apache/cloudstack/backup/ConsolidateVolumesCommand.java new file mode 100644 index 000000000000..7b2bc2245939 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/ConsolidateVolumesCommand.java @@ -0,0 +1,56 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import java.util.List; +import java.util.stream.Collectors; + +public class ConsolidateVolumesCommand extends Command { + + private List volumesToConsolidate; + + private List secondaryStorageUuids; + + private String vmName; + + public ConsolidateVolumesCommand(List volumesToConsolidate, List secondaryStorageUuids, String vmName) { + this.volumesToConsolidate = volumesToConsolidate.stream().map(vol -> (VolumeObjectTO)vol.getTO()).collect(Collectors.toList()); + this.secondaryStorageUuids = secondaryStorageUuids; + this.vmName = vmName; + } + + public List getVolumesToConsolidate() { + return volumesToConsolidate; + } + + public List getSecondaryStorageUuids() { + return secondaryStorageUuids; + } + + public String getVmName() { + return vmName; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferAnswer.java new file mode 100644 index 000000000000..34cf6d4ca34c --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferAnswer.java @@ -0,0 +1,56 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class CreateImageTransferAnswer extends Answer { + private String imageTransferId; + private String transferUrl; + + public CreateImageTransferAnswer() { + } + + public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + + public CreateImageTransferAnswer(CreateImageTransferCommand cmd, boolean success, String details, + String imageTransferId, String transferUrl) { + super(cmd, success, details); + this.imageTransferId = imageTransferId; + this.transferUrl = transferUrl; + } + + public String getImageTransferId() { + return imageTransferId; + } + + public void setImageTransferId(String imageTransferId) { + this.imageTransferId = imageTransferId; + } + + public String getTransferUrl() { + return transferUrl; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } + +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java new file mode 100644 index 000000000000..95b56c9a9c38 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/CreateImageTransferCommand.java @@ -0,0 +1,94 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class CreateImageTransferCommand extends Command { + private String transferId; + private String exportName; + private String socket; + private String direction; + private String checkpointId; + private String file; + private ImageTransfer.Backend backend; + private int idleTimeoutSeconds; + + public CreateImageTransferCommand() { + } + + private CreateImageTransferCommand(String transferId, String direction, String socket, int idleTimeoutSeconds) { + this.transferId = transferId; + this.direction = direction; + this.socket = socket; + this.idleTimeoutSeconds = idleTimeoutSeconds; + } + + public CreateImageTransferCommand(String transferId, String direction, String exportName, String socket, String checkpointId, int idleTimeoutSeconds) { + this(transferId, direction, socket, idleTimeoutSeconds); + this.backend = ImageTransfer.Backend.nbd; + this.exportName = exportName; + this.checkpointId = checkpointId; + } + + public CreateImageTransferCommand(String transferId, String direction, String socket, String file, int idleTimeoutSeconds) { + this(transferId, direction, socket, idleTimeoutSeconds); + if (direction == ImageTransfer.Direction.download.toString()) { + throw new IllegalArgumentException("File backend is only supported for upload"); + } + this.backend = ImageTransfer.Backend.file; + this.file = file; + } + + public String getExportName() { + return exportName; + } + + public String getSocket() { + return socket; + } + + public String getFile() { + return file; + } + + public ImageTransfer.Backend getBackend() { + return backend; + } + + public String getTransferId() { + return transferId; + } + + @Override + public boolean executeInSequence() { + return true; + } + + public String getDirection() { + return direction; + } + + public String getCheckpointId() { + return checkpointId; + } + + public int getIdleTimeoutSeconds() { + return idleTimeoutSeconds; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/DeleteVmCheckpointCommand.java b/core/src/main/java/org/apache/cloudstack/backup/DeleteVmCheckpointCommand.java new file mode 100644 index 000000000000..81cf6c1abfcc --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/DeleteVmCheckpointCommand.java @@ -0,0 +1,60 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.Map; + +import com.cloud.agent.api.Command; + +public class DeleteVmCheckpointCommand extends Command { + private String vmName; + private String checkpointId; + private Map diskPathUuidMap; + private boolean stoppedVM; + + public DeleteVmCheckpointCommand() { + } + + public DeleteVmCheckpointCommand(String vmName, String checkpointId, Map diskPathUuidMap, boolean stoppedVM) { + this.vmName = vmName; + this.checkpointId = checkpointId; + this.diskPathUuidMap = diskPathUuidMap; + this.stoppedVM = stoppedVM; + } + + public String getVmName() { + return vmName; + } + + public String getCheckpointId() { + return checkpointId; + } + + public Map getDiskPathUuidMap() { + return diskPathUuidMap; + } + + public boolean isStoppedVM() { + return stoppedVM; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/FinalizeBackupCompressionCommand.java b/core/src/main/java/org/apache/cloudstack/backup/FinalizeBackupCompressionCommand.java new file mode 100644 index 000000000000..a4cbb69611f7 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/FinalizeBackupCompressionCommand.java @@ -0,0 +1,49 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.BackupDeltaTO; + +import java.util.List; + +public class FinalizeBackupCompressionCommand extends Command { + private boolean cleanup; + + private List backupDeltaTO; + + public FinalizeBackupCompressionCommand(boolean cleanup, List backupDeltaTO) { + this.cleanup = cleanup; + this.backupDeltaTO = backupDeltaTO; + } + + public boolean isCleanup() { + return cleanup; + } + + public List getBackupDeltaTOList() { + return backupDeltaTO; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/FinalizeImageTransferCommand.java b/core/src/main/java/org/apache/cloudstack/backup/FinalizeImageTransferCommand.java new file mode 100644 index 000000000000..84d9b1ff8186 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/FinalizeImageTransferCommand.java @@ -0,0 +1,40 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class FinalizeImageTransferCommand extends Command { + private String transferId; + + public FinalizeImageTransferCommand() { + } + + public FinalizeImageTransferCommand(String transferId) { + this.transferId = transferId; + } + + public String getTransferId() { + return transferId; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/GetBackupStorageStatsCommand.java b/core/src/main/java/org/apache/cloudstack/backup/GetBackupStorageStatsCommand.java new file mode 100644 index 000000000000..1ceeac17e52e --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/GetBackupStorageStatsCommand.java @@ -0,0 +1,66 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class GetBackupStorageStatsCommand extends Command { + private String backupRepoType; + private String backupRepoAddress; + @LogLevel(LogLevel.Log4jLevel.Off) + private String mountOptions; + + public GetBackupStorageStatsCommand(String backupRepoType, String backupRepoAddress, String mountOptions) { + super(); + this.backupRepoType = backupRepoType; + this.backupRepoAddress = backupRepoAddress; + this.mountOptions = mountOptions; + } + + public String getBackupRepoType() { + return backupRepoType; + } + + public void setBackupRepoType(String backupRepoType) { + this.backupRepoType = backupRepoType; + } + + public String getBackupRepoAddress() { + return backupRepoAddress; + } + + public void setBackupRepoAddress(String backupRepoAddress) { + this.backupRepoAddress = backupRepoAddress; + } + + public String getMountOptions() { + return mountOptions == null ? "" : mountOptions; + } + + public void setMountOptions(String mountOptions) { + this.mountOptions = mountOptions; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/PrepareValidationCommand.java b/core/src/main/java/org/apache/cloudstack/backup/PrepareValidationCommand.java new file mode 100644 index 000000000000..bae2fee95774 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/PrepareValidationCommand.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.backup; + +import com.cloud.agent.api.Command; +import com.cloud.utils.Pair; +import org.apache.cloudstack.storage.to.BackupDeltaTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import java.util.List; +import java.util.Set; + +public class PrepareValidationCommand extends Command { + + private List> backupToVolumeList; + + private Set imageStoreSet; + + public PrepareValidationCommand(List> backupToVolumeList, Set imageStoreSet) { + this.backupToVolumeList = backupToVolumeList; + this.imageStoreSet = imageStoreSet; + } + + public List> getBackupToVolumeList() { + return backupToVolumeList; + } + + public Set getImageStoreSet() { + return imageStoreSet; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java index 0bc6865d9e5f..972c2eaf7bb4 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreBackupCommand.java @@ -22,6 +22,7 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.LogLevel; import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import java.util.List; @@ -30,11 +31,15 @@ public class RestoreBackupCommand extends Command { private String backupPath; private String backupRepoType; private String backupRepoAddress; - private List volumePaths; + private List backupVolumesUUIDs; + private List restoreVolumePools; + private List restoreVolumePaths; + private List restoreVolumeSizes; private List backupFiles; private String diskType; private Boolean vmExists; private VirtualMachine.State vmState; + private Integer mountTimeout; protected RestoreBackupCommand() { super(); @@ -72,12 +77,28 @@ public void setBackupRepoAddress(String backupRepoAddress) { this.backupRepoAddress = backupRepoAddress; } - public List getVolumePaths() { - return volumePaths; + public List getRestoreVolumePools() { + return restoreVolumePools; } - public void setVolumePaths(List volumePaths) { - this.volumePaths = volumePaths; + public void setRestoreVolumePools(List restoreVolumePools) { + this.restoreVolumePools = restoreVolumePools; + } + + public List getRestoreVolumePaths() { + return restoreVolumePaths; + } + + public void setRestoreVolumePaths(List restoreVolumePaths) { + this.restoreVolumePaths = restoreVolumePaths; + } + + public List getRestoreVolumeSizes() { + return restoreVolumeSizes; + } + + public void setRestoreVolumeSizes(List restoreVolumeSizes) { + this.restoreVolumeSizes = restoreVolumeSizes; } public List getBackupFiles() { @@ -127,4 +148,20 @@ public void setVmState(VirtualMachine.State vmState) { public boolean executeInSequence() { return true; } + + public List getBackupVolumesUUIDs() { + return backupVolumesUUIDs; + } + + public void setBackupVolumesUUIDs(List backupVolumesUUIDs) { + this.backupVolumesUUIDs = backupVolumesUUIDs; + } + + public Integer getMountTimeout() { + return this.mountTimeout == null ? 0 : this.mountTimeout; + } + + public void setMountTimeout(Integer mountTimeout) { + this.mountTimeout = mountTimeout; + } } diff --git a/core/src/main/java/org/apache/cloudstack/backup/RestoreKbossBackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/RestoreKbossBackupAnswer.java new file mode 100644 index 000000000000..925ceca6b3b3 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreKbossBackupAnswer.java @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +import java.util.Set; + +public class RestoreKbossBackupAnswer extends Answer { + + private Set secondaryStorageUuids; + + public RestoreKbossBackupAnswer(Command command, Set secondaryStorageUuids) { + super(command); + this.secondaryStorageUuids = secondaryStorageUuids; + } + + public RestoreKbossBackupAnswer(Command command, Exception e, Set secondaryStorageUuids) { + super(command, e); + this.secondaryStorageUuids = secondaryStorageUuids; + } + + public Set getSecondaryStorageUuids() { + return secondaryStorageUuids; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/RestoreKbossBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/RestoreKbossBackupCommand.java new file mode 100644 index 000000000000..d173aab324a2 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/RestoreKbossBackupCommand.java @@ -0,0 +1,66 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.utils.Pair; +import org.apache.cloudstack.storage.to.BackupDeltaTO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; + +import java.util.Set; + +public class RestoreKbossBackupCommand extends Command { + + private Set deltasToRemove; + + private Set> backupAndVolumePairs; + + private Set secondaryStorageUrls; + + private boolean quickRestore; + + public RestoreKbossBackupCommand(Set deltasToRemove, Set> backupAndVolumePairs, Set secondaryStorageUrls, + boolean quickRestore) { + this.deltasToRemove = deltasToRemove; + this.backupAndVolumePairs = backupAndVolumePairs; + this.secondaryStorageUrls = secondaryStorageUrls; + this.quickRestore = quickRestore; + } + + @Override + public boolean executeInSequence() { + return false; + } + + public Set getDeltasToRemove() { + return deltasToRemove; + } + + public Set> getBackupAndVolumePairs() { + return backupAndVolumePairs; + } + + public Set getSecondaryStorageUrls() { + return secondaryStorageUrls; + } + + public boolean isQuickRestore() { + return quickRestore; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartBackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/StartBackupAnswer.java new file mode 100644 index 000000000000..d7cbf097df90 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartBackupAnswer.java @@ -0,0 +1,44 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class StartBackupAnswer extends Answer { + private Long checkpointCreateTime; + + public StartBackupAnswer() { + } + + public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + + public StartBackupAnswer(StartBackupCommand cmd, boolean success, String details, Long checkpointCreateTime) { + super(cmd, success, details); + this.checkpointCreateTime = checkpointCreateTime; + } + + public Long getCheckpointCreateTime() { + return checkpointCreateTime; + } + + public void setCheckpointCreateTime(Long checkpointCreateTime) { + this.checkpointCreateTime = checkpointCreateTime; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.java new file mode 100644 index 000000000000..6f1ed6834500 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartBackupCommand.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 +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.Map; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class StartBackupCommand extends Command { + private String vmName; + private String toCheckpointId; + private String fromCheckpointId; + private Long fromCheckpointCreateTime; + private String socket; + private Map diskPathUuidMap; + private boolean stoppedVM; + @LogLevel(LogLevel.Log4jLevel.Off) + private Map diskPathPassphraseMap; + + public StartBackupCommand() { + } + + public StartBackupCommand(String vmName, String toCheckpointId, String fromCheckpointId, Long fromCheckpointCreateTime, + String socket, Map diskPathUuidMap, Map passphrases, boolean stoppedVM) { + this.vmName = vmName; + this.toCheckpointId = toCheckpointId; + this.fromCheckpointId = fromCheckpointId; + this.fromCheckpointCreateTime = fromCheckpointCreateTime; + this.socket = socket; + this.diskPathUuidMap = diskPathUuidMap; + this.diskPathPassphraseMap = passphrases; + this.stoppedVM = stoppedVM; + } + + public String getVmName() { + return vmName; + } + + public String getToCheckpointId() { + return toCheckpointId; + } + + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public Long getFromCheckpointCreateTime() { + return fromCheckpointCreateTime; + } + + public String getSocket() { + return socket; + } + + public Map getDiskPathUuidMap() { + return diskPathUuidMap; + } + + public boolean isIncremental() { + return fromCheckpointId != null && !fromCheckpointId.isEmpty(); + } + + public boolean isStoppedVM() { + return stoppedVM; + } + + public Map getDiskPathPassphraseMap() { + return diskPathPassphraseMap; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerAnswer.java new file mode 100644 index 000000000000..d8c78d3c8807 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerAnswer.java @@ -0,0 +1,56 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class StartNBDServerAnswer extends Answer { + private String imageTransferId; + private String transferUrl; + + public StartNBDServerAnswer() { + } + + public StartNBDServerAnswer(StartNBDServerCommand cmd, boolean success, String details) { + super(cmd, success, details); + } + + public StartNBDServerAnswer(StartNBDServerCommand cmd, boolean success, String details, + String imageTransferId, String transferUrl) { + super(cmd, success, details); + this.imageTransferId = imageTransferId; + this.transferUrl = transferUrl; + } + + public String getImageTransferId() { + return imageTransferId; + } + + public void setImageTransferId(String imageTransferId) { + this.imageTransferId = imageTransferId; + } + + public String getTransferUrl() { + return transferUrl; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } + +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerCommand.java new file mode 100644 index 000000000000..07e50f11fd37 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StartNBDServerCommand.java @@ -0,0 +1,78 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.LogLevel; + +public class StartNBDServerCommand extends Command { + private String transferId; + private String exportName; + private String volumePath; + private String socket; + private String direction; + private String fromCheckpointId; + @LogLevel(LogLevel.Log4jLevel.Off) + private byte[] passphrase; + + public StartNBDServerCommand() { + } + + protected StartNBDServerCommand(String transferId, String exportName, String volumePath, String socket, String direction, String fromCheckpointId, byte[] passphrase) { + this.transferId = transferId; + this.socket = socket; + this.exportName = exportName; + this.volumePath = volumePath; + this.direction = direction; + this.fromCheckpointId = fromCheckpointId; + this.passphrase = passphrase; + } + + public String getExportName() { + return exportName; + } + + public String getSocket() { + return socket; + } + + public String getTransferId() { + return transferId; + } + + @Override + public boolean executeInSequence() { + return true; + } + + public String getVolumePath() { + return volumePath; + } + + public String getDirection() { + return direction; + } + + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public byte[] getPassphrase() { + return passphrase; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StopBackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/StopBackupAnswer.java new file mode 100644 index 000000000000..ce977f31e005 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StopBackupAnswer.java @@ -0,0 +1,30 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; + +public class StopBackupAnswer extends Answer { + + public StopBackupAnswer() { + } + + public StopBackupAnswer(StopBackupCommand cmd, boolean success, String details) { + super(cmd, success, details); + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StopBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StopBackupCommand.java new file mode 100644 index 000000000000..d3055021e9de --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StopBackupCommand.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 +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class StopBackupCommand extends Command { + private String vmName; + private Long vmId; + private Long backupId; + + public StopBackupCommand() { + } + + public StopBackupCommand(String vmName, Long vmId, Long backupId) { + this.vmName = vmName; + this.vmId = vmId; + this.backupId = backupId; + } + + public String getVmName() { + return vmName; + } + + public Long getVmId() { + return vmId; + } + + public Long getBackupId() { + return backupId; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/StopNBDServerCommand.java b/core/src/main/java/org/apache/cloudstack/backup/StopNBDServerCommand.java new file mode 100644 index 000000000000..d75168a22eb2 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/StopNBDServerCommand.java @@ -0,0 +1,46 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; + +public class StopNBDServerCommand extends Command { + private String transferId; + private String direction; + + public StopNBDServerCommand() { + } + + public StopNBDServerCommand(String transferId, String direction) { + this.transferId = transferId; + this.direction = direction; + } + + public String getTransferId() { + return transferId; + } + + public String getDirection() { + return direction; + } + + @Override + public boolean executeInSequence() { + return true; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java index 93855ea17211..34f8d7b8bcdd 100644 --- a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupCommand.java @@ -21,6 +21,7 @@ import com.cloud.agent.api.Command; import com.cloud.agent.api.LogLevel; +import org.apache.cloudstack.storage.to.PrimaryDataStoreTO; import java.util.List; @@ -29,10 +30,23 @@ public class TakeBackupCommand extends Command { private String backupPath; private String backupRepoType; private String backupRepoAddress; + private List volumePools; private List volumePaths; + private Boolean quiesce; @LogLevel(LogLevel.Log4jLevel.Off) private String mountOptions; + // Incremental backup fields (NAS provider; null/empty for legacy full-only callers). + private String mode; // "full" or "incremental"; null => legacy behaviour (script default) + private String bitmapNew; // Checkpoint/bitmap name to create with this backup (timestamp-based) + private String bitmapParent; // Incremental: parent bitmap to read changes since + + // Per-volume parent backup file paths (one per VM volume, ordered by deviceId — same + // order as volumePaths). The script rebases each new qcow2 onto the matching parent. + // Backup file UUIDs differ across volumes, so a single parentPath would have rebased + // every data disk onto the root file. New callers MUST populate parentPaths. + private List parentPaths; + public TakeBackupCommand(String vmName, String backupPath) { super(); this.vmName = vmName; @@ -79,6 +93,14 @@ public void setMountOptions(String mountOptions) { this.mountOptions = mountOptions; } + public List getVolumePools() { + return volumePools; + } + + public void setVolumePools(List volumePools) { + this.volumePools = volumePools; + } + public List getVolumePaths() { return volumePaths; } @@ -87,6 +109,46 @@ public void setVolumePaths(List volumePaths) { this.volumePaths = volumePaths; } + public Boolean getQuiesce() { + return quiesce; + } + + public void setQuiesce(Boolean quiesce) { + this.quiesce = quiesce; + } + + public String getMode() { + return mode; + } + + public void setMode(String mode) { + this.mode = mode; + } + + public String getBitmapNew() { + return bitmapNew; + } + + public void setBitmapNew(String bitmapNew) { + this.bitmapNew = bitmapNew; + } + + public String getBitmapParent() { + return bitmapParent; + } + + public void setBitmapParent(String bitmapParent) { + this.bitmapParent = bitmapParent; + } + + public List getParentPaths() { + return parentPaths; + } + + public void setParentPaths(List parentPaths) { + this.parentPaths = parentPaths; + } + @Override public boolean executeInSequence() { return true; diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeBackupHashCommand.java b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupHashCommand.java new file mode 100644 index 000000000000..7effe40686b2 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeBackupHashCommand.java @@ -0,0 +1,47 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.BackupDeltaTO; + +import java.util.List; + +public class TakeBackupHashCommand extends Command { + + private List backupDeltaTOList; + + private String backupUuid; + + public TakeBackupHashCommand(List backupDeltaTOList, String backupUuid) { + this.backupDeltaTOList = backupDeltaTOList; + this.backupUuid = backupUuid; + } + + public List getBackupDeltaTOList() { + return backupDeltaTOList; + } + + public String getBackupUuid() { + return backupUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupAnswer.java new file mode 100644 index 000000000000..1827c766f573 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupAnswer.java @@ -0,0 +1,59 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.BackupException; + +import java.util.Map; + +public class TakeKbossBackupAnswer extends Answer { + + private Map mapVolumeUuidToNewVolumePath; + private Map> mapVolumeUuidToDeltaPathOnSecondaryAndSize; + private boolean isVmConsistent = true; + + public TakeKbossBackupAnswer(Command command, boolean success, Map mapVolumeUuidToNewVolumePath, + Map> mapVolumeUuidToDeltaPathOnSecondaryAndSize) { + super(command, success, null); + this.mapVolumeUuidToNewVolumePath = mapVolumeUuidToNewVolumePath; + this.mapVolumeUuidToDeltaPathOnSecondaryAndSize = mapVolumeUuidToDeltaPathOnSecondaryAndSize; + } + + public TakeKbossBackupAnswer(Command command, Exception e) { + super(command, e); + if (e instanceof BackupException) { + this.isVmConsistent = ((BackupException)e).isVmConsistent(); + } + } + + public Map getMapVolumeUuidToNewVolumePath() { + return mapVolumeUuidToNewVolumePath; + } + + public Map> getMapVolumeUuidToDeltaPathOnSecondaryAndSize() { + return mapVolumeUuidToDeltaPathOnSecondaryAndSize; + } + + public boolean isVmConsistent() { + return isVmConsistent; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java b/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java new file mode 100644 index 000000000000..b145fa6257b1 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/TakeKbossBackupCommand.java @@ -0,0 +1,92 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import org.apache.cloudstack.storage.to.KbossTO; + +import java.util.List; + +public class TakeKbossBackupCommand extends Command { + + private boolean quiesceVm; + + private boolean runningVM; + + private boolean endChain; + + private String vmName; + + private String imageStoreUrl; + + private List backupChainImageStoreUrls; + + private List kbossTOS; + + private boolean isolated; + + public TakeKbossBackupCommand(boolean quiesceVm, boolean runningVM, boolean endChain, String vmName, String imageStoreUrl, List backupChainImageStoreUrls, List kbossTOS, boolean isolated) { + this.quiesceVm = quiesceVm; + this.runningVM = runningVM; + this.endChain = endChain; + this.vmName = vmName; + this.imageStoreUrl = imageStoreUrl; + this.backupChainImageStoreUrls = backupChainImageStoreUrls; + this.kbossTOS = kbossTOS; + this.isolated = isolated; + } + + public boolean isQuiesceVm() { + return quiesceVm; + } + + public boolean isRunningVM() { + return runningVM; + } + + public boolean isEndChain() { + return endChain; + } + + public String getVmName() { + return vmName; + } + + public String getImageStoreUrl() { + return imageStoreUrl; + } + + public List getBackupChainImageStoreUrls() { + return backupChainImageStoreUrls; + } + + public List getKbossTOs() { + return kbossTOS; + } + + public boolean isIsolated() { + return isolated; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/ValidateKbossVmAnswer.java b/core/src/main/java/org/apache/cloudstack/backup/ValidateKbossVmAnswer.java new file mode 100644 index 000000000000..fcff6a8cce8c --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/ValidateKbossVmAnswer.java @@ -0,0 +1,46 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +public class ValidateKbossVmAnswer extends Answer { + + private boolean bootValidated; + private String screenshotPath; + private String scriptResult; + + public ValidateKbossVmAnswer(Command command, boolean bootValidated, String screenshotPath, String scriptResult) { + super(command); + this.bootValidated = bootValidated; + this.screenshotPath = screenshotPath; + this.scriptResult = scriptResult; + } + + public boolean isBootValidated() { + return bootValidated; + } + + public String getScreenshotPath() { + return screenshotPath; + } + + public String getScriptResult() { + return scriptResult; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/backup/ValidateKbossVmCommand.java b/core/src/main/java/org/apache/cloudstack/backup/ValidateKbossVmCommand.java new file mode 100644 index 000000000000..9e10499f53d9 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/backup/ValidateKbossVmCommand.java @@ -0,0 +1,133 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.backup; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.to.VirtualMachineTO; +import org.apache.cloudstack.storage.to.BackupDeltaTO; + +public class ValidateKbossVmCommand extends Command { + + private VirtualMachineTO vm; + private BackupDeltaTO backupDeltaTO; + + private String scriptToExecute; + + private String scriptArguments; + private String expectedResult; + + private Integer scriptTimeout; + private Integer bootTimeout; + private Integer screenshotWait; + + private boolean takeScreenshot; + private boolean waitForBoot; + private boolean executeScript; + + public ValidateKbossVmCommand(VirtualMachineTO vm, BackupDeltaTO backupDeltaTO) { + this.vm = vm; + this.backupDeltaTO = backupDeltaTO; + } + + public void setScriptToExecute(String scriptToExecute) { + this.scriptToExecute = scriptToExecute; + } + + public void setScriptArguments(String scriptArguments) { + this.scriptArguments = scriptArguments; + } + + public void setExpectedResult(String expectedResult) { + this.expectedResult = expectedResult; + } + + public void setScriptTimeout(Integer scriptTimeout) { + this.scriptTimeout = scriptTimeout; + } + + public void setTakeScreenshot(boolean takeScreenshot) { + this.takeScreenshot = takeScreenshot; + } + + public void setWaitForBoot(boolean waitForBoot) { + this.waitForBoot = waitForBoot; + } + + public void setExecuteScript(boolean executeScript) { + this.executeScript = executeScript; + } + + public void setBootTimeout(Integer bootTimeout) { + this.bootTimeout = bootTimeout; + } + + public void setScreenshotWait(Integer screenshotWait) { + this.screenshotWait = screenshotWait; + } + + public VirtualMachineTO getVm() { + return vm; + } + + public BackupDeltaTO getBackupDeltaTO() { + return backupDeltaTO; + } + + public String getScriptToExecute() { + return scriptToExecute; + } + + public String getScriptArguments() { + return scriptArguments; + } + + public String getExpectedResult() { + return expectedResult; + } + + public Integer getScriptTimeout() { + return scriptTimeout; + } + + public Integer getBootTimeout() { + return bootTimeout; + } + + public Integer getScreenshotWait() { + return screenshotWait; + } + + public boolean isTakeScreenshot() { + return takeScreenshot; + } + + public boolean isWaitForBoot() { + return waitForBoot; + } + + public boolean isExecuteScript() { + return executeScript; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/CommandInfo.java b/core/src/main/java/org/apache/cloudstack/command/CommandInfo.java new file mode 100644 index 000000000000..b9bb702e3459 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/CommandInfo.java @@ -0,0 +1,124 @@ +// 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.command; + +import com.cloud.agent.api.Command; +import com.cloud.serializer.GsonHelper; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import java.util.Date; + +public class CommandInfo { + public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss.SSSZ"; + public static final Gson GSON = GsonHelper.setDefaultGsonConfig(new GsonBuilder().setDateFormat(DATE_FORMAT)); + + long requestSeq; + Command.State state; + Date startTime; + Date updateTime; + String commandName; + String command; + int timeout; + String answerName; + String answer; + + public CommandInfo() { + } + + public CommandInfo(long requestSeq, Command command, Command.State state) { + this.requestSeq = requestSeq; + this.state = state; + this.startTime = this.updateTime = new Date(); + this.commandName = command.getClass().getName(); + this.command = GSON.toJson(command); + this.timeout = command.getWait(); + } + + public long getRequestSeq() { + return requestSeq; + } + + public void setRequestSeq(long requestSeq) { + this.requestSeq = requestSeq; + } + + public Command.State getState() { + return state; + } + + public void setState(Command.State state) { + this.state = state; + this.updateTime = new Date(); + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getUpdateTime() { + return updateTime; + } + + public void setUpdateTime(Date updateTime) { + this.updateTime = updateTime; + } + + public String getCommandName() { + return commandName; + } + + public void setCommandName(String commandName) { + this.commandName = commandName; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public int getTimeout() { + return timeout; + } + + public void setTimeout(int timeout) { + this.timeout = timeout; + } + + public String getAnswerName() { + return answerName; + } + + public void setAnswerName(String answerName) { + this.answerName = answerName; + } + + public String getAnswer() { + return answer; + } + + public void setAnswer(String answer) { + this.answer = answer; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileAnswer.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileAnswer.java new file mode 100644 index 000000000000..e8d27e1fa712 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileAnswer.java @@ -0,0 +1,45 @@ +// +// 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.command; + +import com.cloud.agent.api.Answer; +import org.apache.cloudstack.api.ApiCommandResourceType; + +public class ReconcileAnswer extends Answer { + + ApiCommandResourceType resourceType; + Long resourceId; + + public ApiCommandResourceType getResourceType() { + return resourceType; + } + + public void setResourceType(ApiCommandResourceType resourceType) { + this.resourceType = resourceType; + } + + public Long getResourceId() { + return resourceId; + } + + public void setResourceId(Long resourceId) { + this.resourceId = resourceId; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileCommand.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileCommand.java new file mode 100644 index 000000000000..262aefb30ac6 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileCommand.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.command; + +import com.cloud.agent.api.Command; + +public class ReconcileCommand extends Command { + + @Override + public boolean executeInSequence() { + return false; + } + + @Override + public int getWait() { + return 30; // timeout is 30 seconds + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileCommandUtils.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileCommandUtils.java new file mode 100644 index 000000000000..8acc02a730f1 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileCommandUtils.java @@ -0,0 +1,192 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.gson.JsonSyntaxException; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.text.SimpleDateFormat; + +public class ReconcileCommandUtils { + + protected static final Logger LOGGER = LogManager.getLogger(ReconcileCommandUtils.class.getName()); + + public static void createLogFileForCommand(final String logPath, final Command cmd) { + updateLogFileForCommand(logPath, cmd, Command.State.CREATED); + } + + public static void updateLogFileForCommand(final String logPath, final Command cmd, final Command.State state) { + if (cmd.isReconcile()) { + String logFileName = getLogFileNameForCommand(logPath, cmd); + LOGGER.debug(String.format("Updating log file %s with %s state", logFileName, state)); + File logFile = new File(logFileName); + CommandInfo commandInfo = null; + if (logFile.exists()) { + commandInfo = readLogFileForCommand(logFileName); + logFile.delete(); + } + if (commandInfo == null) { + commandInfo = new CommandInfo(cmd.getRequestSequence(), cmd, state); + } else { + commandInfo.setState(state); + } + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(logFile)); + writer.write(CommandInfo.GSON.toJson(commandInfo)); + writer.close(); + } catch (IOException e) { + LOGGER.error(String.format("Failed to write log file %s", logFile)); + } + } + } + + public static void updateLogFileForCommand(final String logFullPath, final Command.State state) { + File logFile = new File(logFullPath); + LOGGER.debug(String.format("Updating log file %s with %s state", logFile.getName(), state)); + if (!logFile.exists()) { + return; + } + CommandInfo commandInfo = readLogFileForCommand(logFullPath); + if (commandInfo != null) { + commandInfo.setState(state); + } + logFile.delete(); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(logFile)); + writer.write(CommandInfo.GSON.toJson(commandInfo)); + writer.close(); + } catch (IOException e) { + LOGGER.error(String.format("Failed to write log file %s", logFile)); + } + } + + public static void deleteLogFileForCommand(final String logPath, final Command cmd) { + if (cmd.isReconcile()) { + File logFile = new File(getLogFileNameForCommand(logPath, cmd)); + LOGGER.debug(String.format("Removing log file %s", logFile.getName())); + if (logFile.exists()) { + logFile.delete(); + } + } + } + + public static void deleteLogFile(final String logFullPath) { + File logFile = new File(logFullPath); + LOGGER.debug(String.format("Removing log file %s ", logFile.getName())); + if (logFile.exists()) { + logFile.delete(); + } + } + + public static String getLogFileNameForCommand(final String logPath, final Command cmd) { + return String.format("%s/%s-%s.json", logPath, cmd.getRequestSequence(), cmd); + } + + public static CommandInfo readLogFileForCommand(final String logFullPath) { + try { + ObjectMapper objectMapper = new ObjectMapper(); + SimpleDateFormat df = new SimpleDateFormat(CommandInfo.DATE_FORMAT); + objectMapper.setDateFormat(df); + return objectMapper.readValue(new File(logFullPath), CommandInfo.class); + } catch (IOException e) { + LOGGER.error(String.format("Failed to read log file %s: %s", logFullPath, e.getMessage())); + return null; + } + } + + public static Command parseCommandInfo(final CommandInfo commandInfo) { + if (commandInfo.getCommandName() == null || commandInfo.getCommand() == null) { + return null; + } + return parseCommandInfo(commandInfo.getCommandName(), commandInfo.getCommand()); + } + + public static Command parseCommandInfo(final String commandName, final String commandInfo) { + Object parsedObject = null; + try { + Class commandClazz = Class.forName(commandName); + parsedObject = CommandInfo.GSON.fromJson(commandInfo, commandClazz); + } catch (ClassNotFoundException | JsonSyntaxException e) { + LOGGER.error(String.format("Failed to parse command from CommandInfo %s due to %s", commandInfo, e.getMessage())); + } + if (parsedObject != null) { + return (Command) parsedObject; + } + return null; + } + + public static Answer parseAnswerFromCommandInfo(final CommandInfo commandInfo) { + if (commandInfo.getAnswerName() == null || commandInfo.getAnswer() == null) { + return null; + } + return parseAnswerFromAnswerInfo(commandInfo.getAnswerName(), commandInfo.getAnswer()); + } + + public static Answer parseAnswerFromAnswerInfo(final String answerName, final String answerInfo) { + Object parsedObject = null; + try { + Class commandClazz = Class.forName(answerName); + parsedObject = CommandInfo.GSON.fromJson(answerInfo, commandClazz); + } catch (ClassNotFoundException | JsonSyntaxException e) { + LOGGER.error(String.format("Failed to parse answer from answerInfo %s due to %s", answerInfo, e.getMessage())); + } + if (parsedObject != null) { + return (Answer) parsedObject; + } + return null; + } + + public static void updateLogFileWithAnswerForCommand(final String logPath, final Command cmd, final Answer answer) { + if (cmd.isReconcile()) { + String logFileName = getLogFileNameForCommand(logPath, cmd); + LOGGER.debug(String.format("Updating log file %s with answer %s", logFileName, answer)); + File logFile = new File(logFileName); + if (!logFile.exists()) { + return; + } + CommandInfo commandInfo = readLogFileForCommand(logFile.getAbsolutePath()); + if (commandInfo == null) { + return; + } + if (Command.State.STARTED.equals(commandInfo.getState())) { + if (answer.getResult()) { + commandInfo.setState(Command.State.COMPLETED); + } else { + commandInfo.setState(Command.State.FAILED); + } + } + commandInfo.setAnswerName(answer.toString()); + commandInfo.setAnswer(CommandInfo.GSON.toJson(answer)); + try { + BufferedWriter writer = new BufferedWriter(new FileWriter(logFile)); + writer.write(CommandInfo.GSON.toJson(commandInfo)); + writer.close(); + } catch (IOException e) { + LOGGER.error(String.format("Failed to write log file %s", logFile)); + } + } + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileCopyAnswer.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileCopyAnswer.java new file mode 100644 index 000000000000..82a24fa7fa5e --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileCopyAnswer.java @@ -0,0 +1,56 @@ +// +// 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.command; + +import org.apache.cloudstack.storage.volume.VolumeOnStorageTO; + +public class ReconcileCopyAnswer extends ReconcileVolumeAnswer { + + boolean isSkipped = false; + String reason; + + public ReconcileCopyAnswer(boolean isSkipped, String reason) { + super(); + this.isSkipped = isSkipped; + this.reason = reason; + } + + public ReconcileCopyAnswer(boolean isSkipped, boolean result, String reason) { + super(); + this.isSkipped = isSkipped; + this.result = result; + this.reason = reason; + } + + public ReconcileCopyAnswer(VolumeOnStorageTO volumeOnSource, VolumeOnStorageTO volumeOnDestination) { + this.isSkipped = false; + this.result = true; + this.volumeOnSource = volumeOnSource; + this.volumeOnDestination = volumeOnDestination; + } + + public boolean isSkipped() { + return isSkipped; + } + + public String getReason() { + return reason; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileCopyCommand.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileCopyCommand.java new file mode 100644 index 000000000000..36a678833d0f --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileCopyCommand.java @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.command; + +import com.cloud.agent.api.to.DataTO; + +import java.util.Map; + +public class ReconcileCopyCommand extends ReconcileCommand { + + DataTO srcData; + DataTO destData; + Map option; // details of source volume + Map option2; // details of destination volume + + public ReconcileCopyCommand(DataTO srcData, DataTO destData, Map option, Map option2) { + this.srcData = srcData; + this.destData = destData; + this.option = option; + this.option2 = option2; + } + + public DataTO getSrcData() { + return srcData; + } + + public DataTO getDestData() { + return destData; + } + + public Map getOption() { + return option; + } + + public Map getOption2() { + return option2; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateAnswer.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateAnswer.java new file mode 100644 index 000000000000..6313267c7e4c --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateAnswer.java @@ -0,0 +1,68 @@ +// +// 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.command; + +import com.cloud.vm.VirtualMachine; + +import java.util.List; + +public class ReconcileMigrateAnswer extends ReconcileAnswer { + + Long hostId; + String vmName; + VirtualMachine.State vmState; + List vmDisks; + + public ReconcileMigrateAnswer() { + } + + public ReconcileMigrateAnswer(String vmName, VirtualMachine.State vmState) { + this.vmName = vmName; + this.vmState = vmState; + } + + public Long getHostId() { + return hostId; + } + + public void setHostId(Long hostId) { + this.hostId = hostId; + } + + public String getVmName() { + return vmName; + } + + public VirtualMachine.State getVmState() { + return vmState; + } + + public void setVmState(VirtualMachine.State vmState) { + this.vmState = vmState; + } + + public List getVmDisks() { + return vmDisks; + } + + public void setVmDisks(List vmDisks) { + this.vmDisks = vmDisks; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateCommand.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateCommand.java new file mode 100644 index 000000000000..50e1c68a65f6 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateCommand.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.command; + +public class ReconcileMigrateCommand extends ReconcileCommand { + + String vmName; + + public ReconcileMigrateCommand(String vmName) { + this.vmName = vmName; + } + + public String getVmName() { + return vmName; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateVolumeAnswer.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateVolumeAnswer.java new file mode 100644 index 000000000000..ebbd913f9718 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateVolumeAnswer.java @@ -0,0 +1,50 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.command; + +import org.apache.cloudstack.storage.volume.VolumeOnStorageTO; + +import java.util.List; + +public class ReconcileMigrateVolumeAnswer extends ReconcileVolumeAnswer { + + String vmName; + List vmDiskPaths; + + public ReconcileMigrateVolumeAnswer(VolumeOnStorageTO volumeOnSource, VolumeOnStorageTO volumeOnDestination) { + super(volumeOnSource, volumeOnDestination); + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } + + public List getVmDiskPaths() { + return vmDiskPaths; + } + + public void setVmDiskPaths(List vmDiskPaths) { + this.vmDiskPaths = vmDiskPaths; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateVolumeCommand.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateVolumeCommand.java new file mode 100644 index 000000000000..e3e752494068 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileMigrateVolumeCommand.java @@ -0,0 +1,48 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.command; + +import com.cloud.agent.api.to.DataTO; + +public class ReconcileMigrateVolumeCommand extends ReconcileCommand { + + DataTO srcData; + DataTO destData; + String vmName; + + public ReconcileMigrateVolumeCommand(DataTO srcData, DataTO destData) { + this.srcData = srcData; + this.destData = destData; + } + + public DataTO getSrcData() { + return srcData; + } + + public DataTO getDestData() { + return destData; + } + + public String getVmName() { + return vmName; + } + + public void setVmName(String vmName) { + this.vmName = vmName; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/command/ReconcileVolumeAnswer.java b/core/src/main/java/org/apache/cloudstack/command/ReconcileVolumeAnswer.java new file mode 100644 index 000000000000..da10bf23aeeb --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/command/ReconcileVolumeAnswer.java @@ -0,0 +1,46 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.command; + +import org.apache.cloudstack.storage.volume.VolumeOnStorageTO; + +public class ReconcileVolumeAnswer extends ReconcileAnswer { + + // (1) null object: volume is not available. For example the source is secondary storage + // (2) otherwise, if volumeOnSource.getPath() is null, the volume cannot be found on primary storage pool + VolumeOnStorageTO volumeOnSource; + VolumeOnStorageTO volumeOnDestination; + + public ReconcileVolumeAnswer() { + } + + public ReconcileVolumeAnswer(VolumeOnStorageTO volumeOnSource, VolumeOnStorageTO volumeOnDestination) { + this.volumeOnSource = volumeOnSource; + this.volumeOnDestination = volumeOnDestination; + } + + public VolumeOnStorageTO getVolumeOnSource() { + return volumeOnSource; + } + + public VolumeOnStorageTO getVolumeOnDestination() { + return volumeOnDestination; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/HttpDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/HttpDirectTemplateDownloader.java index c4a802ecdbc6..99b84bb645c8 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/HttpDirectTemplateDownloader.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/HttpDirectTemplateDownloader.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.direct.download; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; @@ -32,6 +33,7 @@ import com.cloud.utils.Pair; import com.cloud.utils.UriUtils; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.utils.net.HttpClientCloudStackUserAgent; import com.cloud.utils.storage.QCOW2Utils; import org.apache.commons.collections.MapUtils; import org.apache.commons.httpclient.HttpClient; @@ -39,6 +41,7 @@ import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.io.IOUtils; public class HttpDirectTemplateDownloader extends DirectTemplateDownloaderImpl { @@ -68,6 +71,7 @@ public HttpDirectTemplateDownloader(String url, Long templateId, String destPool protected GetMethod createRequest(String downloadUrl, Map headers) { GetMethod request = new GetMethod(downloadUrl); request.setFollowRedirects(this.isFollowRedirects()); + request.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT); if (MapUtils.isNotEmpty(headers)) { for (String key : headers.keySet()) { request.setRequestHeader(key, headers.get(key)); @@ -111,6 +115,7 @@ protected Pair performDownload() { public boolean checkUrl(String url) { HeadMethod httpHead = new HeadMethod(url); httpHead.setFollowRedirects(this.isFollowRedirects()); + httpHead.getParams().setParameter(HttpMethodParams.USER_AGENT, HttpClientCloudStackUserAgent.CLOUDSTACK_USER_AGENT); try { int responseCode = client.executeMethod(httpHead); if (responseCode != HttpStatus.SC_OK) { diff --git a/core/src/main/java/org/apache/cloudstack/direct/download/HttpsDirectTemplateDownloader.java b/core/src/main/java/org/apache/cloudstack/direct/download/HttpsDirectTemplateDownloader.java index b14d72ac3577..34ab94d55b19 100644 --- a/core/src/main/java/org/apache/cloudstack/direct/download/HttpsDirectTemplateDownloader.java +++ b/core/src/main/java/org/apache/cloudstack/direct/download/HttpsDirectTemplateDownloader.java @@ -97,8 +97,8 @@ protected void createUriRequest(String downloadUrl, Map headers) req = new HttpGet(downloadUrl); setFollowRedirects(this.isFollowRedirects()); if (MapUtils.isNotEmpty(headers)) { - for (String headerKey: headers.keySet()) { - req.setHeader(headerKey, headers.get(headerKey)); + for (Map.Entry entry : headers.entrySet()) { + req.setHeader(entry.getKey(), entry.getValue()); } } } diff --git a/core/src/main/java/org/apache/cloudstack/storage/clvm/command/ClvmLockTransferAnswer.java b/core/src/main/java/org/apache/cloudstack/storage/clvm/command/ClvmLockTransferAnswer.java new file mode 100644 index 000000000000..f3c43c400b2b --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/storage/clvm/command/ClvmLockTransferAnswer.java @@ -0,0 +1,90 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.clvm.command; + +import com.cloud.agent.api.Answer; + +/** + * Answer for ClvmLockTransferCommand, containing lock state information. + * This answer includes the current lock holder information when querying lock state. + */ +public class ClvmLockTransferAnswer extends Answer { + + private String currentLockHostname; + private boolean isActive; + private boolean isOpen; + private String lvAttributes; + + public ClvmLockTransferAnswer(ClvmLockTransferCommand cmd, boolean result, String details) { + super(cmd, result, details); + } + + public ClvmLockTransferAnswer(ClvmLockTransferCommand cmd, boolean result, String details, + String currentLockHostname, boolean isActive, boolean isOpen, + String lvAttributes) { + super(cmd, result, details); + this.currentLockHostname = currentLockHostname; + this.isActive = isActive; + this.isOpen = isOpen; + this.lvAttributes = lvAttributes; + } + + /** + * Get the hostname from lv_host. Retained for diagnostics only — + * do NOT use this to determine lock holder identity. + */ + public String getCurrentLockHostname() { + return currentLockHostname; + } + + public void setCurrentLockHostname(String currentLockHostname) { + this.currentLockHostname = currentLockHostname; + } + + /** + * Whether the LV is locally active on the queried host (lv_attr[4]=='a'). + * This is the authoritative signal for lock holder discovery via fan-out. + */ + public boolean isActive() { + return isActive; + } + + public void setActive(boolean active) { + isActive = active; + } + + /** + * Whether a process has the device file open on the queried host (lv_attr[5]=='o'). + * true means a VM is actively doing I/O on this host right now — do NOT deactivate. + */ + public boolean isOpen() { + return isOpen; + } + + public void setOpen(boolean open) { + isOpen = open; + } + + public String getLvAttributes() { + return lvAttributes; + } + + public void setLvAttributes(String lvAttributes) { + this.lvAttributes = lvAttributes; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/storage/clvm/command/ClvmLockTransferCommand.java b/core/src/main/java/org/apache/cloudstack/storage/clvm/command/ClvmLockTransferCommand.java new file mode 100644 index 000000000000..218fb798869d --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/storage/clvm/command/ClvmLockTransferCommand.java @@ -0,0 +1,99 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.storage.clvm.command; + +import com.cloud.agent.api.Command; + +/** + * Command to transfer CLVM (Clustered LVM) exclusive lock between hosts. + * This enables lightweight volume migration for CLVM storage pools where volumes + * reside in the same Volume Group (VG) but need to be accessed from different hosts. + * + *

Instead of copying volume data (traditional migration), this command simply + * deactivates the LV on the source host and activates it exclusively on the destination host. + * + *

This is significantly faster (10-100x) than traditional migration and uses no network bandwidth. + */ +public class ClvmLockTransferCommand extends Command { + + /** + * Operation to perform on the CLVM volume. + * Maps to lvchange flags for LVM operations. + */ + public enum Operation { + /** Deactivate the volume on this host (-an) */ + DEACTIVATE("-an", "deactivate"), + + /** Activate the volume exclusively on this host (-aey) */ + ACTIVATE_EXCLUSIVE("-aey", "activate exclusively"), + + /** Activate the volume in shared mode on this host (-asy) */ + ACTIVATE_SHARED("-asy", "activate in shared mode"), + + /** Query the current lock state (lvs -o lv_attr,lv_host) */ + QUERY_LOCK_STATE("query", "query lock state"); + + private final String lvchangeFlag; + private final String description; + + Operation(String lvchangeFlag, String description) { + this.lvchangeFlag = lvchangeFlag; + this.description = description; + } + + public String getLvchangeFlag() { + return lvchangeFlag; + } + + public String getDescription() { + return description; + } + } + + private String lvPath; + private Operation operation; + private String volumeUuid; + + public ClvmLockTransferCommand() { + // For serialization + } + + public ClvmLockTransferCommand(Operation operation, String lvPath, String volumeUuid) { + this.operation = operation; + this.lvPath = lvPath; + this.volumeUuid = volumeUuid; + setWait(65); + } + + public String getLvPath() { + return lvPath; + } + + public Operation getOperation() { + return operation; + } + + public String getVolumeUuid() { + return volumeUuid; + } + + @Override + public boolean executeInSequence() { + return false; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/BackupDeleteAnswer.java b/core/src/main/java/org/apache/cloudstack/storage/command/BackupDeleteAnswer.java new file mode 100644 index 000000000000..6cc48ea296d1 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/storage/command/BackupDeleteAnswer.java @@ -0,0 +1,36 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package org.apache.cloudstack.storage.command; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; + +public class BackupDeleteAnswer extends Answer { + + private long backupId; + + public BackupDeleteAnswer(Command command, boolean success, String details) { + super(command, success, details); + backupId = ((DeleteCommand) command).getData().getId(); + } + + public long getBackupId() { + return backupId; + } +} diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/CheckDataStoreStoragePolicyComplainceCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/CheckDataStoreStoragePolicyComplianceCommand.java similarity index 93% rename from core/src/main/java/org/apache/cloudstack/storage/command/CheckDataStoreStoragePolicyComplainceCommand.java rename to core/src/main/java/org/apache/cloudstack/storage/command/CheckDataStoreStoragePolicyComplianceCommand.java index f9544b873ef2..4544e98451f3 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/CheckDataStoreStoragePolicyComplainceCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/CheckDataStoreStoragePolicyComplianceCommand.java @@ -21,12 +21,12 @@ import com.cloud.agent.api.to.StorageFilerTO; -public class CheckDataStoreStoragePolicyComplainceCommand extends StorageSubSystemCommand { +public class CheckDataStoreStoragePolicyComplianceCommand extends StorageSubSystemCommand { String storagePolicyId; private StorageFilerTO storagePool; - public CheckDataStoreStoragePolicyComplainceCommand(String storagePolicyId, StorageFilerTO storagePool) { + public CheckDataStoreStoragePolicyComplianceCommand(String storagePolicyId, StorageFilerTO storagePool) { super(); this.storagePolicyId = storagePolicyId; diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java index aac082a01333..36550420909b 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/CopyCommand.java @@ -93,4 +93,9 @@ public Map getOptions2() { public void setExecuteInSequence(final boolean inSeq) { executeInSequence = inSeq; } + + @Override + public boolean isReconcile() { + return true; + } } diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java index 6f82fa97818d..9aa6f26b5d9c 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/DeleteCommand.java @@ -24,6 +24,8 @@ public final class DeleteCommand extends StorageSubSystemCommand { private DataTO data; + private boolean deleteChain; + public DeleteCommand(final DataTO data) { super(); this.data = data; @@ -42,6 +44,14 @@ public DataTO getData() { return data; } + public void setDeleteChain(boolean deleteChain) { + this.deleteChain = deleteChain; + } + + public boolean isDeleteChain() { + return deleteChain; + } + @Override public void setExecuteInSequence(final boolean inSeq) { diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java index 174302252a55..42926d37cdfe 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/RevertSnapshotCommand.java @@ -25,6 +25,8 @@ public final class RevertSnapshotCommand extends StorageSubSystemCommand { private SnapshotObjectTO dataOnPrimaryStorage; private boolean _executeInSequence = false; + private boolean deleteChain; + public RevertSnapshotCommand(SnapshotObjectTO data, SnapshotObjectTO dataOnPrimaryStorage) { super(); this.data = data; @@ -43,6 +45,14 @@ public SnapshotObjectTO getDataOnPrimaryStorage() { return dataOnPrimaryStorage; } + public boolean isDeleteChain() { + return deleteChain; + } + + public void setDeleteChain(boolean deleteChain) { + this.deleteChain = deleteChain; + } + @Override public void setExecuteInSequence(final boolean executeInSequence) { _executeInSequence = executeInSequence; diff --git a/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java b/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java index 9acfe30bf43f..98db75f39025 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java +++ b/core/src/main/java/org/apache/cloudstack/storage/command/TemplateOrVolumePostUploadCommand.java @@ -60,8 +60,10 @@ public class TemplateOrVolumePostUploadCommand { private String nfsVersion; - public TemplateOrVolumePostUploadCommand(long entityId, String entityUUID, String absolutePath, String checksum, String type, String name, String imageFormat, String dataTo, - String dataToRole) { + private long zoneId; + + public TemplateOrVolumePostUploadCommand(long entityId, String entityUUID, String absolutePath, String checksum, + String type, String name, String imageFormat, String dataTo, String dataToRole, long zoneId) { this.entityId = entityId; this.entityUUID = entityUUID; this.absolutePath = absolutePath; @@ -71,9 +73,7 @@ public TemplateOrVolumePostUploadCommand(long entityId, String entityUUID, Strin this.imageFormat = imageFormat; this.dataTo = dataTo; this.dataToRole = dataToRole; - } - - public TemplateOrVolumePostUploadCommand() { + this.zoneId = zoneId; } public String getRemoteEndPoint() { @@ -224,4 +224,8 @@ public void setProcessTimeout(long processTimeout) { public long getProcessTimeout() { return processTimeout; } + + public long getZoneId() { + return zoneId; + } } diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/BackupDeltaTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/BackupDeltaTO.java new file mode 100644 index 000000000000..662a2d486560 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/storage/to/BackupDeltaTO.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.to; + +import com.cloud.agent.api.to.DataObjectType; +import com.cloud.agent.api.to.DataStoreTO; +import com.cloud.agent.api.to.DataTO; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.Storage; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class BackupDeltaTO implements DataTO { + private DataStoreTO dataStoreTO; + + private Hypervisor.HypervisorType hypervisorType; + + private String path; + + private String screenshotPath; + + private Storage.ImageFormat format; + + // When set, represents the Backup ID, not the delta ID. + private long id = 0; + + public BackupDeltaTO(DataStoreTO dataStoreTO, Hypervisor.HypervisorType hypervisorType, String path) { + this.dataStoreTO = dataStoreTO; + this.hypervisorType = hypervisorType; + this.path = path; + this.format = Storage.ImageFormat.QCOW2; + } + + public BackupDeltaTO(long id, DataStoreTO dataStoreTO, Hypervisor.HypervisorType hypervisorType, String path) { + this(dataStoreTO, hypervisorType, path); + this.id = id; + } + + @Override + public DataObjectType getObjectType() { + return DataObjectType.BACKUP; + } + + @Override + public DataStoreTO getDataStore() { + return dataStoreTO; + } + + @Override + public Hypervisor.HypervisorType getHypervisorType() { + return hypervisorType; + } + + @Override + public String getPath() { + return path; + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public Storage.ImageFormat getFormat() { + return this.format; + } + + public void setScreenshotPath(String screenshotPath) { + this.screenshotPath = screenshotPath; + } + + public String getScreenshotPath() { + return screenshotPath; + } + + public void setPath(String path) { + this.path = path; + } + + @Override + public String toString() { + return new ReflectionToStringBuilder(this, ToStringStyle.JSON_STYLE).setExcludeFieldNames("id").toString(); + } +} diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/DeltaMergeTreeTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/DeltaMergeTreeTO.java new file mode 100644 index 000000000000..143cf6fe22bb --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/storage/to/DeltaMergeTreeTO.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.cloudstack.storage.to; + +import com.cloud.agent.api.to.DataTO; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import java.util.List; + +public class DeltaMergeTreeTO { + + VolumeObjectTO volumeObjectTO; + DataTO parent; + DataTO child; + List grandChildren; + + public DeltaMergeTreeTO(VolumeObjectTO volumeObjectTO, DataTO parent, DataTO child, List grandChildren) { + this.volumeObjectTO = volumeObjectTO; + this.parent = parent; + this.child = child; + this.grandChildren = grandChildren; + } + + public VolumeObjectTO getVolumeObjectTO() { + return volumeObjectTO; + } + + public DataTO getParent() { + return parent; + } + + public void setParent(DataTO parent) { + this.parent = parent; + } + + public DataTO getChild() { + return child; + } + + public List getGrandChildren() { + return grandChildren; + } + + public void addGrandChild(DataTO grandChild) { + grandChildren.add(grandChild); + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/KbossTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/KbossTO.java new file mode 100644 index 000000000000..4a3f53a6dad4 --- /dev/null +++ b/core/src/main/java/org/apache/cloudstack/storage/to/KbossTO.java @@ -0,0 +1,110 @@ +package org.apache.cloudstack.storage.to; +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +import java.util.LinkedList; +import java.util.List; + +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +public class KbossTO { + + private String pathBackupParentOnSecondary; + private VolumeObjectTO volumeObjectTO; + private String deltaPathOnPrimary; + private String parentDeltaPathOnPrimary; + private String deltaPathOnSecondary; + private String oldVolumePath; + + private DeltaMergeTreeTO deltaMergeTreeTO; + + private List deltaPaths; + + public KbossTO(VolumeObjectTO volumeObjectTO, LinkedList deltaPaths) { + this.volumeObjectTO = volumeObjectTO; + this.deltaPaths = deltaPaths; + } + + public KbossTO(VolumeObjectTO volumeObjectTO, String deltaPathOnPrimary, String deltaPathOnSecondary, LinkedList deltaPaths) { + this.volumeObjectTO = volumeObjectTO; + this.deltaPathOnPrimary = deltaPathOnPrimary; + this.deltaPathOnSecondary = deltaPathOnSecondary; + this.deltaPaths = deltaPaths; + } + + public String getPathBackupParentOnSecondary() { + return pathBackupParentOnSecondary; + } + + public VolumeObjectTO getVolumeObjectTO() { + return volumeObjectTO; + } + + public DeltaMergeTreeTO getDeltaMergeTreeTO() { + return deltaMergeTreeTO; + } + + public List getDeltaPaths() { + return deltaPaths; + } + + public String getDeltaPathOnPrimary() { + return deltaPathOnPrimary; + } + + public String getDeltaPathOnSecondary() { + return deltaPathOnSecondary; + } + + public String getParentDeltaPathOnPrimary() { + return parentDeltaPathOnPrimary; + } + + public void setParentDeltaPathOnPrimary(String parentDeltaPathOnPrimary) { + this.parentDeltaPathOnPrimary = parentDeltaPathOnPrimary; + } + + public void setPathBackupParentOnSecondary(String pathBackupParentOnSecondary) { + this.pathBackupParentOnSecondary = pathBackupParentOnSecondary; + } + + public void setDeltaMergeTreeTO(DeltaMergeTreeTO deltaMergeTreeTO) { + this.deltaMergeTreeTO = deltaMergeTreeTO; + } + + public void setDeltaPathOnPrimary(String deltaPathOnPrimary) { + this.deltaPathOnPrimary = deltaPathOnPrimary; + } + + public void setDeltaPathOnSecondary(String deltaPathOnSecondary) { + this.deltaPathOnSecondary = deltaPathOnSecondary; + } + + public String getOldVolumePath() { + return oldVolumePath; + } + + public void setOldVolumePath(String oldVolumePath) { + this.oldVolumePath = oldVolumePath; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java index 9d64ab841214..7643f80bbaac 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java +++ b/core/src/main/java/org/apache/cloudstack/storage/to/SnapshotObjectTO.java @@ -35,18 +35,27 @@ public class SnapshotObjectTO extends DownloadableObjectTO implements DataTO { private VolumeObjectTO volume; private String parentSnapshotPath; private DataStoreTO dataStore; + private DataStoreTO imageStore; + private boolean kvmIncrementalSnapshot = false; private String vmName; private String name; private HypervisorType hypervisorType; private long id; private boolean quiescevm; private String[] parents; + private DataStoreTO parentStore; + private String checkpointPath; private Long physicalSize = (long) 0; private long accountId; public SnapshotObjectTO() { } + @Override + public DataObjectType getObjectType() { + return DataObjectType.SNAPSHOT; + } + public SnapshotObjectTO(SnapshotInfo snapshot) { this.path = snapshot.getPath(); this.setId(snapshot.getId()); @@ -57,27 +66,28 @@ public SnapshotObjectTO(SnapshotInfo snapshot) { this.setVmName(vol.getAttachedVmName()); } - SnapshotInfo parentSnapshot = snapshot.getParent(); - ArrayList parentsArry = new ArrayList(); - if (parentSnapshot != null) { - this.parentSnapshotPath = parentSnapshot.getPath(); - while(parentSnapshot != null) { - parentsArry.add(parentSnapshot.getPath()); - parentSnapshot = parentSnapshot.getParent(); - } - parents = parentsArry.toArray(new String[parentsArry.size()]); - ArrayUtils.reverse(parents); - } - this.dataStore = snapshot.getDataStore().getTO(); this.setName(snapshot.getName()); this.hypervisorType = snapshot.getHypervisorType(); this.quiescevm = false; - } - @Override - public DataObjectType getObjectType() { - return DataObjectType.SNAPSHOT; + this.checkpointPath = snapshot.getCheckpointPath(); + this.kvmIncrementalSnapshot = snapshot.isKvmIncrementalSnapshot(); + + SnapshotInfo parentSnapshot = snapshot.getParent(); + + if (parentSnapshot == null || (HypervisorType.KVM.equals(snapshot.getHypervisorType()) && !parentSnapshot.isKvmIncrementalSnapshot())) { + return; + } + + ArrayList parentsArray = new ArrayList<>(); + this.parentSnapshotPath = parentSnapshot.getPath(); + while (parentSnapshot != null) { + parentsArray.add(parentSnapshot.getPath()); + parentSnapshot = parentSnapshot.getParent(); + } + parents = parentsArray.toArray(new String[parentsArray.size()]); + ArrayUtils.reverse(parents); } @Override @@ -89,6 +99,30 @@ public void setDataStore(DataStoreTO store) { this.dataStore = store; } + public DataStoreTO getImageStore() { + return imageStore; + } + + public void setImageStore(DataStoreTO imageStore) { + this.imageStore = imageStore; + } + + public boolean isKvmIncrementalSnapshot() { + return kvmIncrementalSnapshot; + } + + public void setKvmIncrementalSnapshot(boolean kvmIncrementalSnapshot) { + this.kvmIncrementalSnapshot = kvmIncrementalSnapshot; + } + + public String getCheckpointPath() { + return checkpointPath; + } + + public void setCheckpointPath(String checkpointPath) { + this.checkpointPath = checkpointPath; + } + @Override public String getPath() { return this.path; @@ -176,6 +210,14 @@ public void setAccountId(long accountId) { this.accountId = accountId; } + public DataStoreTO getParentStore() { + return parentStore; + } + + public void setParentStore(DataStoreTO parentStore) { + this.parentStore = parentStore; + } + @Override public String toString() { return new StringBuilder("SnapshotTO[datastore=").append(dataStore).append("|volume=").append(volume).append("|path").append(path).append("]").toString(); diff --git a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java index 4d1d0bf90971..5b1d4c573b68 100644 --- a/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java +++ b/core/src/main/java/org/apache/cloudstack/storage/to/VolumeObjectTO.java @@ -32,9 +32,12 @@ import com.cloud.storage.Volume; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import java.io.Serializable; import java.util.Arrays; +import java.util.List; +import java.util.Set; -public class VolumeObjectTO extends DownloadableObjectTO implements DataTO { +public class VolumeObjectTO extends DownloadableObjectTO implements DataTO, Serializable { private String uuid; private Volume.Type volumeType; private DataStoreTO dataStore; @@ -76,6 +79,9 @@ public class VolumeObjectTO extends DownloadableObjectTO implements DataTO { @LogLevel(LogLevel.Log4jLevel.Off) private byte[] passphrase; private String encryptFormat; + private List checkpointPaths; + private Set checkpointImageStoreUrls; + private Set deltasToRemove; public VolumeObjectTO() { @@ -112,8 +118,8 @@ public VolumeObjectTO(VolumeInfo volume) { iopsWriteRate = volume.getIopsWriteRate(); iopsWriteRateMax = volume.getIopsWriteRateMax(); iopsWriteRateMaxLength = volume.getIopsWriteRateMaxLength(); - cacheMode = volume.getCacheMode(); hypervisorType = volume.getHypervisorType(); + setCacheMode(volume.getCacheMode()); setDeviceId(volume.getDeviceId()); this.migrationOptions = volume.getMigrationOptions(); this.directDownload = volume.isDirectDownload(); @@ -122,6 +128,8 @@ public VolumeObjectTO(VolumeInfo volume) { this.passphrase = volume.getPassphrase(); this.encryptFormat = volume.getEncryptFormat(); this.followRedirects = volume.isFollowRedirects(); + this.checkpointPaths = volume.getCheckpointPaths(); + this.checkpointImageStoreUrls = volume.getCheckpointImageStoreUrls(); } public String getUuid() { @@ -337,6 +345,10 @@ public void setDeviceId(Long deviceId) { } public void setCacheMode(DiskCacheMode cacheMode) { + if (DiskCacheMode.HYPERVISOR_DEFAULT.equals(cacheMode) && !Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + this.cacheMode = DiskCacheMode.NONE; + return; + } this.cacheMode = cacheMode; } @@ -397,4 +409,29 @@ public void clearPassphrase() { public boolean requiresEncryption() { return passphrase != null && passphrase.length > 0; } + + + public List getCheckpointPaths() { + return checkpointPaths; + } + + public void setCheckpointPaths(List checkpointPaths) { + this.checkpointPaths = checkpointPaths; + } + + public Set getCheckpointImageStoreUrls() { + return checkpointImageStoreUrls; + } + + public void setCheckpointImageStoreUrls(Set checkpointImageStoreUrls) { + this.checkpointImageStoreUrls = checkpointImageStoreUrls; + } + + public Set getDeltasToRemove() { + return deltasToRemove; + } + + public void setDeltasToRemove(Set deltasToRemove) { + this.deltasToRemove = deltasToRemove; + } } diff --git a/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml index 175d45e26752..fcbcb18c2bdf 100644 --- a/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml +++ b/core/src/main/resources/META-INF/cloudstack/backup/spring-core-lifecycle-backup-context-inheritable.xml @@ -29,4 +29,9 @@ + + + + + diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index 01c568d78916..cf43b8527a97 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -339,6 +339,10 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> + + + @@ -366,4 +370,9 @@ + + + + + diff --git a/test/selenium/common/__init__.py b/core/src/main/resources/META-INF/cloudstack/dns/module.properties similarity index 96% rename from test/selenium/common/__init__.py rename to core/src/main/resources/META-INF/cloudstack/dns/module.properties index 13a83393a912..a2bb467be751 100644 --- a/test/selenium/common/__init__.py +++ b/core/src/main/resources/META-INF/cloudstack/dns/module.properties @@ -1,3 +1,4 @@ +# # 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 @@ -14,3 +15,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +# + +name=dns +parent=core diff --git a/core/src/main/resources/META-INF/cloudstack/dns/spring-core-lifecycle-dns-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/dns/spring-core-lifecycle-dns-context-inheritable.xml new file mode 100644 index 000000000000..27cac9400284 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/dns/spring-core-lifecycle-dns-context-inheritable.xml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/core/src/main/resources/META-INF/cloudstack/kms/module.properties b/core/src/main/resources/META-INF/cloudstack/kms/module.properties new file mode 100644 index 000000000000..98e38d7cd8f6 --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/kms/module.properties @@ -0,0 +1,21 @@ +# +# 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=kms +parent=core diff --git a/core/src/main/resources/META-INF/cloudstack/kms/spring-core-lifecycle-kms-context-inheritable.xml b/core/src/main/resources/META-INF/cloudstack/kms/spring-core-lifecycle-kms-context-inheritable.xml new file mode 100644 index 000000000000..9226eef8fc1a --- /dev/null +++ b/core/src/main/resources/META-INF/cloudstack/kms/spring-core-lifecycle-kms-context-inheritable.xml @@ -0,0 +1,29 @@ + + + + + + + + diff --git a/core/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java b/core/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java index 042bec9d2168..4ddadab999a4 100644 --- a/core/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java +++ b/core/src/test/java/com/cloud/agent/resource/virtualnetwork/ConfigHelperTest.java @@ -57,6 +57,7 @@ import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRule; import com.cloud.agent.resource.virtualnetwork.model.LoadBalancerRules; import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.cloud.network.lb.LoadBalancingRule.LbSslCert; import com.cloud.network.Networks.TrafficType; public class ConfigHelperTest { @@ -223,15 +224,18 @@ public void testDeleteIpAlias() { protected LoadBalancerConfigCommand generateLoadBalancerConfigCommand() { final List lbs = new ArrayList<>(); final List dests = new ArrayList<>(); + final LbSslCert lbSslCert = new LbSslCert("cert", "key", "password", "chain", "fingerprint", false); dests.add(new LbDestination(80, 8080, "10.1.10.2", false)); dests.add(new LbDestination(80, 8080, "10.1.10.2", true)); - lbs.add(new LoadBalancerTO(UUID.randomUUID().toString(), "64.10.1.10", 80, "tcp", "algo", false, false, false, dests)); + LoadBalancerTO loadBalancerTO = new LoadBalancerTO(UUID.randomUUID().toString(), "64.10.1.10", 80, "tcp", "algo", false, false, false, dests); + loadBalancerTO.setLbSslCert(lbSslCert); + lbs.add(loadBalancerTO); final LoadBalancerTO[] arrayLbs = new LoadBalancerTO[lbs.size()]; lbs.toArray(arrayLbs); final NicTO nic = new NicTO(); - final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false); + final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false, 0L); cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2"); cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); diff --git a/core/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java b/core/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java index 201242564ba6..ed819bb7c68f 100644 --- a/core/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java +++ b/core/src/test/java/com/cloud/agent/resource/virtualnetwork/VirtualRoutingResourceTest.java @@ -417,8 +417,6 @@ private void verifyArgs(final SetNetworkACLCommand cmd, final String script, fin // FIXME Check the json content assertEquals(VRScripts.UPDATE_CONFIG, script); assertEquals(VRScripts.NETWORK_ACL_CONFIG, args); - // assertEquals(args, " -d eth3 -M 01:23:45:67:89:AB -i 192.168.1.1 -m 24 -a Egress:ALL:0:0:192.168.0.1/24-192.168.0.2/24:ACCEPT:," + - // "Ingress:ICMP:0:0:192.168.0.1/24-192.168.0.2/24:DROP:,Ingress:TCP:20:80:192.168.0.1/24-192.168.0.2/24:ACCEPT:,"); break; case 2: assertEquals(VRScripts.UPDATE_CONFIG, script); @@ -464,8 +462,6 @@ protected SetupGuestNetworkCommand generateSetupGuestNetworkCommand() { private void verifyArgs(final SetupGuestNetworkCommand cmd, final String script, final String args) { // TODO Check the contents of the json file - //assertEquals(script, VRScripts.VPC_GUEST_NETWORK); - //assertEquals(args, " -C -M 01:23:45:67:89:AB -d eth4 -i 10.1.1.2 -g 10.1.1.1 -m 24 -n 10.1.1.0 -s 8.8.8.8,8.8.4.4 -e cloud.test"); } @Test @@ -783,7 +779,7 @@ protected LoadBalancerConfigCommand generateLoadBalancerConfigCommand1() { final LoadBalancerTO[] arrayLbs = new LoadBalancerTO[lbs.size()]; lbs.toArray(arrayLbs); final NicTO nic = new NicTO(); - final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false); + final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, null, "1000", false, 50000L); cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2"); cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); return cmd; @@ -799,7 +795,7 @@ protected LoadBalancerConfigCommand generateLoadBalancerConfigCommand2() { lbs.toArray(arrayLbs); final NicTO nic = new NicTO(); nic.setIp("10.1.10.2"); - final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, Long.valueOf(1), "1000", false); + final LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(arrayLbs, "64.10.2.10", "10.1.10.2", "192.168.1.2", nic, Long.valueOf(1), "1000", false, 50000L); cmd.setAccessDetail(NetworkElementCommand.ROUTER_IP, "10.1.10.2"); cmd.setAccessDetail(NetworkElementCommand.ROUTER_NAME, ROUTERNAME); return cmd; diff --git a/core/src/test/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRuleTest.java b/core/src/test/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRuleTest.java new file mode 100644 index 000000000000..f2b25a5f78ec --- /dev/null +++ b/core/src/test/java/com/cloud/agent/resource/virtualnetwork/model/LoadBalancerRuleTest.java @@ -0,0 +1,63 @@ +// 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.resource.virtualnetwork.model; + +import org.junit.Assert; +import org.junit.Test; + +public class LoadBalancerRuleTest { + + @Test + public void testSslCertEntry() { + String name = "name"; + String cert = "cert"; + String key = "key1"; + String chain = "chain"; + String password = "password"; + LoadBalancerRule.SslCertEntry sslCertEntry = new LoadBalancerRule.SslCertEntry(name, cert, key, chain, password); + + Assert.assertEquals(name, sslCertEntry.getName()); + Assert.assertEquals(cert, sslCertEntry.getCert()); + Assert.assertEquals(key, sslCertEntry.getKey()); + Assert.assertEquals(chain, sslCertEntry.getChain()); + Assert.assertEquals(password, sslCertEntry.getPassword()); + + String name2 = "name2"; + String cert2 = "cert2"; + String key2 = "key2"; + String chain2 = "chain2"; + String password2 = "password2"; + + sslCertEntry.setName(name2); + sslCertEntry.setCert(cert2); + sslCertEntry.setKey(key2); + sslCertEntry.setChain(chain2); + sslCertEntry.setPassword(password2); + + Assert.assertEquals(name2, sslCertEntry.getName()); + Assert.assertEquals(cert2, sslCertEntry.getCert()); + Assert.assertEquals(key2, sslCertEntry.getKey()); + Assert.assertEquals(chain2, sslCertEntry.getChain()); + Assert.assertEquals(password2, sslCertEntry.getPassword()); + + LoadBalancerRule loadBalancerRule = new LoadBalancerRule(); + loadBalancerRule.setSslCerts(new LoadBalancerRule.SslCertEntry[]{sslCertEntry}); + + Assert.assertEquals(1, loadBalancerRule.getSslCerts().length); + Assert.assertEquals(sslCertEntry, loadBalancerRule.getSslCerts()[0]); + } +} diff --git a/core/src/test/java/com/cloud/agent/transport/ResponseTest.java b/core/src/test/java/com/cloud/agent/transport/ResponseTest.java new file mode 100644 index 000000000000..06869b42eb9d --- /dev/null +++ b/core/src/test/java/com/cloud/agent/transport/ResponseTest.java @@ -0,0 +1,46 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package com.cloud.agent.transport; + +import junit.framework.TestCase; + +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; +import org.junit.Assert; +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.CheckS2SVpnConnectionsAnswer; + +import com.cloud.agent.transport.Request.Version; + +public class ResponseTest extends TestCase { + protected Logger logger = LogManager.getLogger(getClass()); + + public void testCheckS2SVpnConnectionsAnswer() { + logger.info("Testing CheckS2SVpnConnectionsAnswer"); + String content = "[{\"com.cloud.agent.api.CheckS2SVpnConnectionsAnswer\":{\"ipToConnected\":{\"10.0.53.13\":true}," + + "\"ipToDetail\":{\"10.0.53.13\":\"IPsec SA found;Site-to-site VPN have connected\"}," + + "\"details\":\"10.0.53.13:0:IPsec SA found;Site-to-site VPN have connected\\u0026\\n\"," + + "\"result\":true,\"contextMap\":{},\"wait\":0,\"bypassHostMaintenance\":false}}]"; + Response response = new Response(Version.v2, 1L, 2L, 3L, 1L, (short)1, content); + Answer answer = response.getAnswer(); + Assert.assertTrue(answer instanceof CheckS2SVpnConnectionsAnswer); + } + +} diff --git a/core/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java b/core/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java index 2a282cbeca8b..073f976719b5 100644 --- a/core/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java +++ b/core/src/test/java/com/cloud/network/HAProxyConfiguratorTest.java @@ -31,6 +31,7 @@ import com.cloud.agent.api.routing.LoadBalancerConfigCommand; import com.cloud.agent.api.to.LoadBalancerTO; import com.cloud.network.lb.LoadBalancingRule.LbDestination; +import com.cloud.network.lb.LoadBalancingRule.LbSslCert; import java.util.List; import java.util.ArrayList; @@ -78,13 +79,14 @@ public void testGenerateConfigurationLoadBalancerConfigCommand() { LoadBalancerTO[] lba = new LoadBalancerTO[1]; lba[0] = lb; HAProxyConfigurator hpg = new HAProxyConfigurator(); - LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false); + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L); String result = genConfig(hpg, cmd); - assertTrue("keepalive disabled should result in 'mode http' in the resulting haproxy config", result.contains("mode http")); + assertTrue("keepalive disabled should result in 'option httpclose' in the resulting haproxy config", result.contains("\toption httpclose")); - cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true); + cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true, 0L); result = genConfig(hpg, cmd); - assertTrue("keepalive enabled should not result in 'mode http' in the resulting haproxy config", !result.contains("mode http")); + assertTrue("keepalive enabled should result in 'no option httpclose' in the resulting haproxy config", result.contains("\tno option httpclose")); + // TODO // create lb command // setup tests for @@ -92,6 +94,27 @@ public void testGenerateConfigurationLoadBalancerConfigCommand() { // httpmode } + /** + * Test method for {@link com.cloud.network.HAProxyConfigurator#generateConfiguration(com.cloud.agent.api.routing.LoadBalancerConfigCommand)}. + */ + @Test + public void testGenerateConfigurationLoadBalancerIdleTimeoutConfigCommand() { + LoadBalancerTO lb = new LoadBalancerTO("1", "10.2.0.1", 80, "http", "bla", false, false, false, null); + LoadBalancerTO[] lba = new LoadBalancerTO[1]; + lba[0] = lb; + HAProxyConfigurator hpg = new HAProxyConfigurator(); + + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true, 0L); + String result = genConfig(hpg, cmd); + assertTrue("idleTimeout of 0 should not generate 'timeout server' in the resulting haproxy config", !result.contains("\ttimeout server")); + assertTrue("idleTimeout of 0 should not generate 'timeout client' in the resulting haproxy config", !result.contains("\ttimeout client")); + + cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "4", true, 1234L); + result = genConfig(hpg, cmd); + assertTrue("idleTimeout of 1234 should result in 'timeout server 1234' in the resulting haproxy config", result.contains("\ttimeout server 1234")); + assertTrue("idleTimeout of 1234 should result in 'timeout client 1234' in the resulting haproxy config", result.contains("\ttimeout client 1234")); + } + /** * Test method for {@link com.cloud.network.HAProxyConfigurator#generateConfiguration(com.cloud.agent.api.routing.LoadBalancerConfigCommand)}. */ @@ -105,7 +128,7 @@ public void testGenerateConfigurationLoadBalancerProxyProtocolConfigCommand() { LoadBalancerTO[] lba = new LoadBalancerTO[1]; lba[0] = lb; HAProxyConfigurator hpg = new HAProxyConfigurator(); - LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false); + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L); String result = genConfig(hpg, cmd); assertTrue("'send-proxy' should result if protocol is 'tcp-proxy'", result.contains("send-proxy")); } @@ -117,11 +140,24 @@ public void generateConfigurationTestWithCidrList() { LoadBalancerTO[] lba = new LoadBalancerTO[1]; lba[0] = lb; HAProxyConfigurator hpg = new HAProxyConfigurator(); - LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false); + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L); String result = genConfig(hpg, cmd); Assert.assertTrue(result.contains("acl network_allowed src 1.1.1.1 2.2.2.2/24 \n\ttcp-request connection reject if !network_allowed")); } + @Test + public void generateConfigurationTestWithSslCert() { + LoadBalancerTO lb = new LoadBalancerTO("1", "10.2.0.1", 443, "ssl", "roundrobin", false, false, false, null); + final LbSslCert lbSslCert = new LbSslCert("cert", "key", "password", "chain", "fingerprint", false); + lb.setLbSslCert(lbSslCert); + LoadBalancerTO[] lba = new LoadBalancerTO[1]; + lba[0] = lb; + HAProxyConfigurator hpg = new HAProxyConfigurator(); + LoadBalancerConfigCommand cmd = new LoadBalancerConfigCommand(lba, "10.0.0.1", "10.1.0.1", "10.1.1.1", null, 1L, "12", false, 0L); + String result = genConfig(hpg, cmd); + Assert.assertTrue(result.contains("bind 10.2.0.1:443 ssl crt /etc/cloudstack/ssl/10_2_0_1-443.pem")); + } + private String genConfig(HAProxyConfigurator hpg, LoadBalancerConfigCommand cmd) { String[] sa = hpg.generateConfiguration(cmd); StringBuilder sb = new StringBuilder(); diff --git a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingCommandTest.java b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingCommandTest.java index 3f68422d5026..f021009cec0e 100644 --- a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingCommandTest.java +++ b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckGuestOsMappingCommandTest.java @@ -25,8 +25,6 @@ import com.cloud.agent.api.CheckGuestOsMappingCommand; import org.junit.Test; -import com.cloud.agent.api.AgentControlCommand; - public class CheckGuestOsMappingCommandTest { @Test diff --git a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java index 287769d6a76d..a696049608e5 100644 --- a/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java +++ b/core/src/test/java/org/apache/cloudstack/api/agent/test/CheckOnHostCommandTest.java @@ -189,6 +189,11 @@ public Long getManagementServerId() { return 2L; }; + @Override + public Long getLastManagementServerId() { + return null; + }; + @Override public Date getRemoved() { Date date = null; @@ -279,6 +284,11 @@ public ResourceState getResourceState() { public CPU.CPUArch getArch() { return CPU.CPUArch.amd64; } + + @Override + public String getStorageAccessGroups() { + return null; + } }; CheckOnHostCommand cohc = new CheckOnHostCommand(host); diff --git a/core/src/test/java/org/apache/cloudstack/command/ReconcileCommandUtilsTest.java b/core/src/test/java/org/apache/cloudstack/command/ReconcileCommandUtilsTest.java new file mode 100644 index 000000000000..f69ada58e1a4 --- /dev/null +++ b/core/src/test/java/org/apache/cloudstack/command/ReconcileCommandUtilsTest.java @@ -0,0 +1,69 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.command; + +import com.cloud.agent.api.Command; +import com.cloud.agent.api.MigrateCommand; +import com.cloud.agent.api.to.VirtualMachineTO; + +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.io.File; + +public class ReconcileCommandUtilsTest { + + final static String COMMANDS_LOG_PATH = "/tmp"; + + @Test + public void createAndReadAndDeleteLogFilesForCommand() { + final String vmName = "TestVM"; + final String destIp = "DestinationHost"; + final VirtualMachineTO vmTO = Mockito.mock(VirtualMachineTO.class); + final MigrateCommand command = new MigrateCommand(vmName, destIp, false, vmTO, false); + long requestSequence = 10000L; + command.setRequestSequence(requestSequence); + + String logFile = ReconcileCommandUtils.getLogFileNameForCommand(COMMANDS_LOG_PATH, command); + + ReconcileCommandUtils.createLogFileForCommand(COMMANDS_LOG_PATH, command); + Assert.assertTrue((new File(logFile).exists())); + + CommandInfo commandInfo = ReconcileCommandUtils.readLogFileForCommand(logFile); + Assert.assertNotNull(commandInfo); + Assert.assertEquals(command.getClass().getName(), commandInfo.getCommandName()); + Assert.assertEquals(Command.State.CREATED, commandInfo.getState()); + + Command parseCommand = ReconcileCommandUtils.parseCommandInfo(commandInfo); + System.out.println("command state is " + commandInfo); + Assert.assertNotNull(parseCommand); + Assert.assertTrue(parseCommand instanceof MigrateCommand); + Assert.assertEquals(vmName,((MigrateCommand) parseCommand).getVmName()); + Assert.assertEquals(destIp,((MigrateCommand) parseCommand).getDestinationIp()); + + ReconcileCommandUtils.updateLogFileForCommand(COMMANDS_LOG_PATH, command, Command.State.PROCESSING); + CommandInfo newCommandInfo = ReconcileCommandUtils.readLogFileForCommand(logFile); + System.out.println("new command state is " + newCommandInfo); + Assert.assertNotNull(newCommandInfo); + Assert.assertEquals(command.getClass().getName(), newCommandInfo.getCommandName()); + Assert.assertEquals(Command.State.PROCESSING, newCommandInfo.getState()); + + ReconcileCommandUtils.deleteLogFileForCommand(COMMANDS_LOG_PATH, command); + Assert.assertFalse((new File(logFile).exists())); + } +} diff --git a/debian/changelog b/debian/changelog index 6bd9e5e95112..41f97748a0b5 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,48 +1,36 @@ -cloudstack (4.20.4.0-SNAPSHOT) unstable; urgency=low +cloudstack (4.23.0.0-SNAPSHOT) unstable; urgency=low - * Update the version to 4.20.4.0-SNAPSHOT + * Update the version to 4.23.0.0-SNAPSHOT - -- the Apache CloudStack project Mon, 13 Apr 2026 12:36:00 +0530 + -- the Apache CloudStack project Fri, 22 May 2026 10:20:00 -0300 -cloudstack (4.20.3.0) unstable; urgency=low +cloudstack (4.22.1.0) unstable; urgency=low - * Update the version to 4.20.3.0 + * Update the version to 4.22.1.0 - -- the Apache CloudStack project Mon, 06 Apr 2026 17:22:19 +0530 + -- the Apache CloudStack project Mon, 11 May 2026 20:26:07 +0530 -cloudstack (4.20.3.0-SNAPSHOT) unstable; urgency=low +cloudstack (4.22.0.0) unstable; urgency=low - * Update the version to 4.20.3.0-SNAPSHOT + * Update the version to 4.22.0.0 - -- the Apache CloudStack project Fri, 24 Oct 2025 08:36:00 +0200 + -- the Apache CloudStack project Thu, 30 Oct 2025 19:23:55 +0530 -cloudstack (4.20.2.0) unstable; urgency=low +cloudstack (4.21.0.0) unstable; urgency=low - * Update the version to 4.20.2.0 + * Update the version to 4.21.0.0 - -- the Apache CloudStack project Thu, 16 Oct 2025 11:42:56 +0200 + -- the Apache CloudStack project Fri, 22 Aug 2025 11:42:37 +0530 -cloudstack (4.20.2.0-SNAPSHOT) unstable; urgency=low +cloudstack (4.21.0.0-SNAPSHOT) unstable; urgency=low - * Update the version to 4.20.2.0-SNAPSHOT - - -- the Apache CloudStack project Mon, 29 Jan 2024 10:21:52 +0530 - -cloudstack (4.20.1.0) unstable; urgency=low - - * Update the version to 4.20.1.0 - - -- the Apache CloudStack project Fri, 30 May 2025 12:43:00 +0530 - -cloudstack (4.20.1.0-SNAPSHOT) unstable; urgency=low - - * Update the version to 4.20.1.0-SNAPSHOT + * Update the version to 4.21.0.0-SNAPSHOT -- the Apache CloudStack project Tue, 19 Nov 2024 08:54:07 -0300 -cloudstack (4.20.1.0-SNAPSHOT-SNAPSHOT) unstable; urgency=low +cloudstack (4.21.0.0-SNAPSHOT-SNAPSHOT) unstable; urgency=low - * Update the version to 4.20.1.0-SNAPSHOT-SNAPSHOT + * Update the version to 4.21.0.0-SNAPSHOT-SNAPSHOT -- the Apache CloudStack project Mon, 29 Jan 2024 10:21:52 +0530 @@ -232,7 +220,7 @@ cloud (4.0.0-rc2) unstable; urgency=low * Bumping the version to 4.0 RC2 -- Wido den Hollander Mon, 24 Sep 2012 11:30:29 +0200 - + cloud (4.0.0-rc1) unstable; urgency=low * Bumping the version to 4.0 RC1 diff --git a/debian/cloudstack-agent.install b/debian/cloudstack-agent.install index 58715e0746ba..0b9e874cb423 100644 --- a/debian/cloudstack-agent.install +++ b/debian/cloudstack-agent.install @@ -16,6 +16,7 @@ # under the License. /etc/cloudstack/agent/agent.properties +/etc/cloudstack/agent/uefi.properties /etc/cloudstack/agent/environment.properties /etc/cloudstack/agent/log4j-cloud.xml /etc/default/cloudstack-agent diff --git a/debian/cloudstack-agent.postinst b/debian/cloudstack-agent.postinst index 758af6e068ff..cd070c2f7853 100755 --- a/debian/cloudstack-agent.postinst +++ b/debian/cloudstack-agent.postinst @@ -23,7 +23,7 @@ case "$1" in configure) OLDCONFDIR="/etc/cloud/agent" NEWCONFDIR="/etc/cloudstack/agent" - CONFFILES="agent.properties log4j.xml log4j-cloud.xml" + CONFFILES="agent.properties uefi.properties log4j.xml log4j-cloud.xml" mkdir -m 0755 -p /usr/share/cloudstack-agent/tmp diff --git a/debian/cloudstack-cli.install b/debian/cloudstack-cli.install index 978b68af62a4..13a83393a912 100644 --- a/debian/cloudstack-cli.install +++ b/debian/cloudstack-cli.install @@ -5,9 +5,9 @@ # 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 diff --git a/debian/cloudstack-common.install b/debian/cloudstack-common.install index 08f56d4f117f..51fc5bf2a661 100644 --- a/debian/cloudstack-common.install +++ b/debian/cloudstack-common.install @@ -27,6 +27,7 @@ /usr/share/cloudstack-common/scripts/vm/hypervisor/versions.sh /usr/share/cloudstack-common/scripts/vm/hypervisor/vmware/* /usr/share/cloudstack-common/scripts/vm/hypervisor/xenserver/* +/usr/share/cloudstack-common/scripts/vm/hypervisor/external/provisioner/* /usr/share/cloudstack-common/lib/* /usr/share/cloudstack-common/vms/* /usr/bin/cloudstack-set-guest-password diff --git a/debian/cloudstack-common.postinst b/debian/cloudstack-common.postinst index aa99edaee064..b11e6a3fe502 100644 --- a/debian/cloudstack-common.postinst +++ b/debian/cloudstack-common.postinst @@ -19,14 +19,14 @@ set -e CLOUDUTILS_DIR="/usr/share/pyshared/" -DIST_DIR=$(python3 -c "from distutils.sysconfig import get_python_lib; print(get_python_lib(1))") -if which pycompile >/dev/null 2>&1; then - pycompile -p cloudstack-common -fi +# distutils was removed in Python 3.12 (Ubuntu 24.04); the Debian/Ubuntu-patched +# sysconfig 'deb_system' scheme gives the same /usr/lib/python3/dist-packages path. +DIST_DIR=$(python3 -c "import sysconfig; print(sysconfig.get_path('platlib', 'deb_system'))") -if which pycompile >/dev/null 2>&1; then - pycompile -p cloudstack-common /usr/share/cloudstack-common +if command -v py3compile >/dev/null 2>&1; then + py3compile -p cloudstack-common 2>/dev/null || echo "Warning: py3compile failed for cloudstack-common" >&2 + py3compile -p cloudstack-common /usr/share/cloudstack-common 2>/dev/null || echo "Warning: py3compile failed for cloudstack-common (/usr/share/cloudstack-common)" >&2 fi cp $CLOUDUTILS_DIR/cloud_utils.py $DIST_DIR diff --git a/debian/cloudstack-docs.install b/debian/cloudstack-docs.install index 978b68af62a4..13a83393a912 100644 --- a/debian/cloudstack-docs.install +++ b/debian/cloudstack-docs.install @@ -5,9 +5,9 @@ # 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 diff --git a/debian/cloudstack-management.install b/debian/cloudstack-management.install index 3d0d7e238149..befc7049c30e 100644 --- a/debian/cloudstack-management.install +++ b/debian/cloudstack-management.install @@ -22,6 +22,9 @@ /etc/cloudstack/management/java.security.ciphers /etc/cloudstack/management/log4j-cloud.xml /etc/cloudstack/management/config.json +/etc/cloudstack/extensions/Proxmox/proxmox.sh +/etc/cloudstack/extensions/HyperV/hyperv.py +/etc/cloudstack/extensions/MaaS/maas.py /etc/default/cloudstack-management /etc/security/limits.d/cloudstack-limits.conf /etc/sudoers.d/cloudstack diff --git a/debian/cloudstack-management.postinst b/debian/cloudstack-management.postinst index d5d50a4718c3..fde3ba96de0a 100755 --- a/debian/cloudstack-management.postinst +++ b/debian/cloudstack-management.postinst @@ -59,6 +59,9 @@ if [ "$1" = configure ]; then chown -R cloud:cloud /usr/share/cloudstack-management/templates find /usr/share/cloudstack-management/templates -type d -exec chmod 0770 {} \; + chmod -R 0755 /etc/cloudstack/extensions + chown -R cloud:cloud /etc/cloudstack/extensions + ln -sf ${CONFDIR}/log4j-cloud.xml ${CONFDIR}/log4j2.xml # Add jdbc MySQL driver settings to db.properties if not present diff --git a/debian/control b/debian/control index a773844c27cf..cdf663ef8906 100644 --- a/debian/control +++ b/debian/control @@ -1,10 +1,10 @@ Source: cloudstack Section: libs Priority: extra -Maintainer: Wido den Hollander +Maintainer: The Apache CloudStack Team Build-Depends: debhelper (>= 9), openjdk-17-jdk | java17-sdk | java17-jdk | zulu-17 | openjdk-11-jdk | java11-sdk | java11-jdk | zulu-11, genisoimage, python-mysql.connector | python3-mysql.connector | mysql-connector-python-py3, maven (>= 3) | maven3, - python (>= 2.7) | python2 (>= 2.7), python3 (>= 3), python-setuptools, python3-setuptools, + python3 (>= 3), python3-setuptools, nodejs (>= 12), lsb-release, dh-systemd | debhelper (>= 13) Standards-Version: 3.8.1 Homepage: http://www.cloudstack.org/ @@ -24,7 +24,7 @@ Description: CloudStack server library Package: cloudstack-agent Architecture: all -Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat +Depends: ${python:Depends}, ${python3:Depends}, openjdk-17-jre-headless | java17-runtime-headless | java17-runtime | zulu-17, cloudstack-common (= ${source:Version}), lsb-base (>= 9), openssh-client, qemu-kvm (>= 2.5) | qemu-system-x86 (>= 5.2), libvirt-bin (>= 1.3) | libvirt-daemon-system (>= 3.0), iproute2, ebtables, vlan, ipset, python3-libvirt, ethtool, iptables, cryptsetup, rng-tools, rsync, ovmf, swtpm, lsb-release, ufw, apparmor, cpu-checker, libvirt-daemon-driver-storage-rbd, sysstat, python3-libnbd, socat Recommends: init-system-helpers Conflicts: cloud-agent, cloud-agent-libs, cloud-agent-deps, cloud-agent-scripts Description: CloudStack agent diff --git a/debian/rules b/debian/rules index da49ca55c1b9..327447823308 100755 --- a/debian/rules +++ b/debian/rules @@ -63,12 +63,14 @@ override_dh_auto_install: # cloudstack-management mkdir $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/server mkdir $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/management + mkdir -p $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/extensions mkdir -p $(DESTDIR)/$(SYSCONFDIR)/security/limits.d/ mkdir -p $(DESTDIR)/$(SYSCONFDIR)/sudoers.d/ mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management/lib mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management/setup mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management/templates/systemvm + mkdir -p $(DESTDIR)/usr/share/$(PACKAGE)-management/cks/conf mkdir $(DESTDIR)/var/log/$(PACKAGE)/management mkdir $(DESTDIR)/var/cache/$(PACKAGE)/management mkdir $(DESTDIR)/var/log/$(PACKAGE)/ipallocator @@ -79,10 +81,12 @@ override_dh_auto_install: cp -r client/target/classes/META-INF/webapp $(DESTDIR)/usr/share/$(PACKAGE)-management/webapp cp server/target/conf/* $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/server/ cp client/target/conf/* $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/management/ + cp -r extensions/* $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/extensions/ cp client/target/cloud-client-ui-$(VERSION).jar $(DESTDIR)/usr/share/$(PACKAGE)-management/lib/cloudstack-$(VERSION).jar cp client/target/lib/*jar $(DESTDIR)/usr/share/$(PACKAGE)-management/lib/ cp -r engine/schema/dist/systemvm-templates/* $(DESTDIR)/usr/share/$(PACKAGE)-management/templates/systemvm/ - rm -rf $(DESTDIR)/usr/share/$(PACKAGE)-management/templates/systemvm/md5sum.txt + cp -r plugins/integrations/kubernetes-service/src/main/resources/conf/* $(DESTDIR)/usr/share/$(PACKAGE)-management/cks/conf/ + rm -rf $(DESTDIR)/usr/share/$(PACKAGE)-management/templates/systemvm/sha512sum.txt # Bundle cmk in cloudstack-management wget https://github.com/apache/cloudstack-cloudmonkey/releases/latest/download/cmk.linux.x86-64 -O $(DESTDIR)/usr/bin/cmk @@ -91,12 +95,20 @@ override_dh_auto_install: # nast hack for a couple of configuration files mv $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/server/cloudstack-limits.conf $(DESTDIR)/$(SYSCONFDIR)/security/limits.d/ mv $(DESTDIR)/$(SYSCONFDIR)/$(PACKAGE)/server/cloudstack-sudoers $(DESTDIR)/$(SYSCONFDIR)/sudoers.d/$(PACKAGE) + sed -i '/requiretty/d' $(DESTDIR)/$(SYSCONFDIR)/sudoers.d/$(PACKAGE) chmod 0440 $(DESTDIR)/$(SYSCONFDIR)/sudoers.d/$(PACKAGE) install -D client/target/utilities/bin/cloud-update-xenserver-licenses $(DESTDIR)/usr/bin/cloudstack-update-xenserver-licenses + + install -D plugins/integrations/kubernetes-service/src/main/resources/conf/etcd-node.yml $(DESTDIR)/usr/share/$(PACKAGE)-management/cks/conf/etcd-node.yml + install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node.yml $(DESTDIR)/usr/share/$(PACKAGE)-management/cks/conf/k8s-control-node.yml + install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-control-node-add.yml $(DESTDIR)/usr/share/$(PACKAGE)-management/cks/conf/k8s-control-node-add.yml + install -D plugins/integrations/kubernetes-service/src/main/resources/conf/k8s-node.yml $(DESTDIR)/usr/share/$(PACKAGE)-management/cks/conf/k8s-node.yml + # Remove configuration in /ur/share/cloudstack-management/webapps/client/WEB-INF # This should all be in /etc/cloudstack/management ln -s ../../..$(SYSCONFDIR)/$(PACKAGE)/management $(DESTDIR)/usr/share/$(PACKAGE)-management/conf + ln -s ../../..$(SYSCONFDIR)/$(PACKAGE)/extensions $(DESTDIR)/usr/share/$(PACKAGE)-management/extensions ln -s ../../../var/log/$(PACKAGE)/management $(DESTDIR)/usr/share/$(PACKAGE)-management/logs install -d -m0755 debian/$(PACKAGE)-management/lib/systemd/system diff --git a/deps/install-non-oss.sh b/deps/install-non-oss.sh index 56cfc2cba9cc..e40946ce312c 100755 --- a/deps/install-non-oss.sh +++ b/deps/install-non-oss.sh @@ -85,4 +85,7 @@ mvn install:install-file -Dfile=juniper-contrail-api-1.0-SNAPSHOT.jar -DgroupId= # From https://github.com/radu-todirica/tungsten-api/raw/master/net/juniper/tungsten/juniper-tungsten-api/2.0/juniper-tungsten-api-2.0.jar mvn install:install-file -Dfile=juniper-tungsten-api-2.0.jar -DgroupId=net.juniper.tungsten -DartifactId=juniper-tungsten-api -Dversion=2.0 -Dpackaging=jar +# Netris Integration +mvn install:install-file -Dfile=netris-java-sdk-1.0.0.jar -DgroupId=io.netris -DartifactId=netris-java-sdk -Dversion=1.0.0 -Dpackaging=jar + exit 0 diff --git a/developer/pom.xml b/developer/pom.xml index 9608a2ba0f5b..0a0979ee0379 100644 --- a/developer/pom.xml +++ b/developer/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT @@ -66,7 +66,7 @@ org.codehaus.mojo properties-maven-plugin - 1.0-alpha-2 + 1.2.1 initialize diff --git a/engine/api/pom.xml b/engine/api/pom.xml index 17090998d6b1..cb50ef0cd46b 100644 --- a/engine/api/pom.xml +++ b/engine/api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml 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 238a78e89aff..c7238a513693 100644 --- a/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java +++ b/engine/api/src/main/java/com/cloud/vm/VirtualMachineManager.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.vm; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.net.URI; import java.util.HashMap; import java.util.LinkedHashMap; @@ -57,6 +59,8 @@ */ public interface VirtualMachineManager extends Manager { + String KVM_BLANK_VM_TEMPLATE_NAME = "kvm-blank-vm-template"; + ConfigKey ExecuteInSequence = new ConfigKey<>("Advanced", Boolean.class, "execute.in.sequence.hypervisor.commands", "false", "If set to true, start, stop, reboot, copy and migrate commands will be serialized on the agent side. If set to false the commands are executed in parallel. Default value is false.", false); @@ -104,9 +108,14 @@ public interface VirtualMachineManager extends Manager { ConfigKey VmSyncPowerStateTransitioning = new ConfigKey<>("Advanced", Boolean.class, "vm.sync.power.state.transitioning", "true", "Whether to sync power states of the transitioning and stalled VMs while processing VM power reports.", false); + ConfigKey SystemVmEnableUserData = new ConfigKey<>(Boolean.class, "systemvm.userdata.enabled", "Advanced", "false", + "Enable user data for system VMs. When enabled, the CPVM, SSVM, and Router system VMs will use the values from the global settings console.proxy.vm.userdata, secstorage.vm.userdata, and virtual.router.userdata, respectively, to provide cloud-init user data to the VM.", + true, ConfigKey.Scope.Zone, null); interface Topics { String VM_POWER_STATE = "vm.powerstate"; + String VM_LIFECYCLE_STATE = "vm.lifecycle.state"; + String VM_ACTION = "vm.action"; } /** @@ -122,6 +131,7 @@ interface Topics { * @param defaultNetwork The default network for the VM. * @param rootDiskOffering For created VMs not based on templates, root disk offering specifies the root disk. * @param dataDiskOfferings Data disks to attach to the VM. + * @param dataDiskDeviceIds Device Ids to assign the data disks to. * @param auxiliaryNetworks additional networks to attach the VMs to. * @param plan How to deploy the VM. * @param hyperType Hypervisor type @@ -129,11 +139,11 @@ interface Topics { * @throws InsufficientCapacityException If there are insufficient capacity to deploy this vm. */ void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, DiskOfferingInfo rootDiskOfferingInfo, - List dataDiskOfferings, LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, - HypervisorType hyperType, Map> extraDhcpOptions, Map datadiskTemplateToDiskOfferingMap) throws InsufficientCapacityException; + List dataDiskOfferings, List dataDiskDeviceIds, LinkedHashMap> auxiliaryNetworks, DeploymentPlan plan, + HypervisorType hyperType, Map> extraDhcpOptions, Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; void allocate(String vmInstanceName, VirtualMachineTemplate template, ServiceOffering serviceOffering, - LinkedHashMap> networkProfiles, DeploymentPlan plan, HypervisorType hyperType) throws InsufficientCapacityException; + LinkedHashMap> networkProfiles, DeploymentPlan plan, HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; void start(String vmUuid, Map params); @@ -175,15 +185,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); @@ -224,6 +225,8 @@ NicProfile addVmToNetwork(VirtualMachine vm, Network network, NicProfile request Boolean updateDefaultNicForVM(VirtualMachine vm, Nic nic, Nic defaultNic); + boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) throws ResourceUnavailableException; + /** * @param vm * @param network @@ -271,7 +274,7 @@ static String getHypervisorHostname(String name) { * - Remove the references of the VM and its volumes, nics, IPs from database * - Keep the VM as it is on the hypervisor */ - boolean unmanage(String vmUuid); + Pair unmanage(String vmUuid, Long paramHostId); UserVm restoreVirtualMachine(long vmId, Long newTemplateId, Long rootDiskOfferingId, boolean expunge, Map details) throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException; @@ -313,4 +316,8 @@ void checkDeploymentPlan(VirtualMachine virtualMachine, VirtualMachineTemplate t ServiceOffering serviceOffering, Account systemAccount, DeploymentPlan plan) throws InsufficientServerCapacityException; + boolean isBlankInstanceDefaultTemplate(VirtualMachineTemplate template); + + boolean isBlankInstance(VirtualMachineTemplate template); + } 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 97e2a979aa48..109a44488ec4 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 @@ -76,42 +76,65 @@ public interface NetworkOrchestrationService { */ Long RVRHandoverTime = 10000L; - ConfigKey MinVRVersion = new ConfigKey(String.class, MinVRVersionCK, "Advanced", "4.10.0", + ConfigKey MinVRVersion = new ConfigKey<>(String.class, MinVRVersionCK, "Advanced", "4.10.0", "What version should the Virtual Routers report", true, ConfigKey.Scope.Zone, null); - ConfigKey NetworkLockTimeout = new ConfigKey(Integer.class, NetworkLockTimeoutCK, "Network", "600", - "Lock wait timeout (seconds) while implementing Network", true, Scope.Global, null); + ConfigKey NetworkLockTimeout = new ConfigKey<>(Integer.class, NetworkLockTimeoutCK, "Network", "600", + "Lock wait timeout (seconds) while implementing Network", true, Scope.Global, null); - ConfigKey DeniedRoutes = new ConfigKey(String.class, "denied.routes", "Network", "", + ConfigKey DeniedRoutes = new ConfigKey<>(String.class, "denied.routes", "Network", "", "Routes that are denied, can not be used for Static Routes creation for the VPC Private Gateway", true, ConfigKey.Scope.Zone, null); - ConfigKey GuestDomainSuffix = new ConfigKey(String.class, GuestDomainSuffixCK, "Network", "cloud.internal", - "Default domain name for vms inside virtualized networks fronted by router", true, ConfigKey.Scope.Zone, null); + ConfigKey GuestDomainSuffix = new ConfigKey<>(String.class, GuestDomainSuffixCK, "Network", "cloud.internal", + "Default domain name for vms inside virtualized networks fronted by router", true, ConfigKey.Scope.Zone, null); - ConfigKey NetworkThrottlingRate = new ConfigKey("Network", Integer.class, NetworkThrottlingRateCK, "200", - "Default data transfer rate in megabits per second allowed in network.", true, ConfigKey.Scope.Zone); + ConfigKey NetworkThrottlingRate = new ConfigKey<>("Network", Integer.class, NetworkThrottlingRateCK, "200", + "Default data transfer rate in megabits per second allowed in network.", true, ConfigKey.Scope.Zone); - ConfigKey PromiscuousMode = new ConfigKey("Advanced", Boolean.class, "network.promiscuous.mode", "false", + ConfigKey DhcpLeaseTimeout = new ConfigKey<>("Network", Integer.class, "dhcp.lease.timeout", "0", + "DHCP lease time in seconds for VMs. Use 0 for infinite lease time (default). A non-zero value sets the lease duration in seconds.", + true, ConfigKey.Scope.Zone); + + ConfigKey PromiscuousMode = new ConfigKey<>("Advanced", Boolean.class, "network.promiscuous.mode", "false", "Whether to allow or deny promiscuous mode on NICs for applicable network elements such as for vswitch/dvswitch portgroups.", true); - ConfigKey MacAddressChanges = new ConfigKey("Advanced", Boolean.class, "network.mac.address.changes", "true", + ConfigKey MacAddressChanges = new ConfigKey<>("Advanced", Boolean.class, "network.mac.address.changes", "true", "Whether to allow or deny mac address changes on NICs for applicable network elements such as for vswitch/dvswitch porgroups.", true); - ConfigKey ForgedTransmits = new ConfigKey("Advanced", Boolean.class, "network.forged.transmits", "true", + ConfigKey ForgedTransmits = new ConfigKey<>("Advanced", Boolean.class, "network.forged.transmits", "true", "Whether to allow or deny forged transmits on NICs for applicable network elements such as for vswitch/dvswitch portgroups.", true); - ConfigKey MacLearning = new ConfigKey("Advanced", Boolean.class, "network.mac.learning", "false", + ConfigKey MacLearning = new ConfigKey<>("Advanced", Boolean.class, "network.mac.learning", "false", "Whether to allow or deny MAC learning on nics for applicable network elements such as for dvswitch portgroups.", true); - ConfigKey RollingRestartEnabled = new ConfigKey("Advanced", Boolean.class, "network.rolling.restart", "true", + ConfigKey RollingRestartEnabled = new ConfigKey<>("Advanced", Boolean.class, "network.rolling.restart", "true", "Whether to allow or deny rolling restart of network routers.", true); - static final ConfigKey TUNGSTEN_ENABLED = new ConfigKey<>(Boolean.class, "tungsten.plugin.enable", "Advanced", "false", + ConfigKey TUNGSTEN_ENABLED = new ConfigKey<>(Boolean.class, "tungsten.plugin.enable", "Advanced", "false", "Indicates whether to enable the Tungsten plugin", false, ConfigKey.Scope.Zone, null); - static final ConfigKey NSX_ENABLED = new ConfigKey<>(Boolean.class, "nsx.plugin.enable", "Advanced", "false", + ConfigKey NSX_ENABLED = new ConfigKey<>(Boolean.class, "nsx.plugin.enable", "Advanced", "false", "Indicates whether to enable the NSX plugin", false, ConfigKey.Scope.Zone, null); + ConfigKey NETRIS_ENABLED = new ConfigKey<>(Boolean.class, "netris.plugin.enable", "Advanced", "false", + "Indicates whether to enable the Netris plugin", false, ConfigKey.Scope.Zone, null); + ConfigKey NETWORK_LB_HAPROXY_MAX_CONN = new ConfigKey<>( + "Network", + Integer.class, + "network.loadbalancer.haproxy.max.conn", + "4096", + "Load Balancer(haproxy) maximum number of concurrent connections(global max)", + true, + Scope.Global); + ConfigKey NETWORK_LB_HAPROXY_IDLE_TIMEOUT = new ConfigKey<>( + "Network", + Long.class, + "network.loadbalancer.haproxy.idle.timeout", + "50000", + "Load Balancer(haproxy) idle timeout in milliseconds. Use 0 for infinite.", + true, + Scope.Global); + ConfigKey VmNetworkThrottlingRate = new ConfigKey("Network", Integer.class, "vm.network.throttling.rate", "200", "Default data transfer rate in megabits per second allowed in User vm's default network.", true, ConfigKey.Scope.Zone); @@ -122,6 +145,8 @@ List setupNetwork(Account owner, NetworkOffering offering, Ne boolean errorIfAlreadySetup, Long domainId, ACLType aclType, Boolean subdomainAccess, Long vpcId, Boolean isDisplayNetworkEnabled) throws ConcurrentOperationException; + boolean isIsolationMethodNetworkExtension(Long networkOfferingId); + void allocate(VirtualMachineProfile vm, LinkedHashMap> networks, Map> extraDhcpOptions) throws InsufficientCapacityException, ConcurrentOperationException; @@ -129,7 +154,7 @@ void allocate(VirtualMachineProfile vm, LinkedHashMap extraDhcpOptions); @@ -158,16 +183,15 @@ void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationC Pair implementNetwork(long networkId, DeployDestination dest, ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; - Map getExtraDhcpOptions(long nicId); - /** * Returns all extra dhcp options which are set on the provided nic * @param nicId * @return map which maps the dhcp value on it's option code */ + Map getExtraDhcpOptions(long nicId); + /** * prepares vm nic change for migration - * * This method will be called in migration transaction before the vm migration. * @param vm * @param dest @@ -176,7 +200,6 @@ void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationC /** * commit vm nic change for migration - * * This method will be called in migration transaction after the successful * vm migration. * @param src @@ -186,7 +209,6 @@ void prepare(VirtualMachineProfile profile, DeployDestination dest, ReservationC /** * rollback vm nic change for migration - * * This method will be called in migaration transaction after vm migration * failure. * @param src @@ -207,6 +229,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); @@ -266,7 +293,7 @@ NicProfile prepareNic(VirtualMachineProfile vmProfile, DeployDestination dest, R void releaseNic(VirtualMachineProfile vmProfile, Nic nic) throws ConcurrentOperationException, ResourceUnavailableException; NicProfile createNicForVm(Network network, NicProfile requested, ReservationContext context, VirtualMachineProfile vmProfile, boolean prepare) - throws InsufficientVirtualNetworkCapacityException, InsufficientAddressCapacityException, ConcurrentOperationException, InsufficientCapacityException, + throws ConcurrentOperationException, InsufficientCapacityException, ResourceUnavailableException; NetworkProfile convertNetworkToNetworkProfile(long networkId); @@ -277,7 +304,7 @@ boolean restartNetwork(Long networkId, Account callerAccount, User callerUser, b boolean shutdownNetworkElementsAndResources(ReservationContext context, boolean b, Network network); void implementNetworkElementsAndResources(DeployDestination dest, ReservationContext context, Network network, NetworkOffering findById) - throws ConcurrentOperationException, InsufficientAddressCapacityException, ResourceUnavailableException, InsufficientCapacityException; + throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException; Map finalizeServicesAndProvidersForNetwork(NetworkOffering offering, Long physicalNetworkId); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index 84e110a8940e..141596407fe8 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -23,6 +23,8 @@ import java.util.Set; import com.cloud.exception.ResourceAllocationException; +import com.cloud.storage.Storage; +import com.cloud.utils.Pair; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; @@ -83,6 +85,17 @@ public interface VolumeOrchestrationService { "The maximum size for a volume (in GiB).", true); + ConfigKey VolumeAllocationAlgorithm = new ConfigKey<>( + String.class, + "volume.allocation.algorithm", + "Advanced", + "random", + "Order in which storage pool within a cluster will be considered for volume allocation. The value can be 'random', 'firstfit', 'userdispersing', or 'firstfitleastconsumed'.", + true, + ConfigKey.Scope.Global, null, null, null, null, null, + ConfigKey.Kind.Select, + "random,firstfit,userdispersing,firstfitleastconsumed"); + VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) throws ConcurrentOperationException, StorageUnavailableException; @@ -107,9 +120,9 @@ VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, void destroyVolume(Volume volume); DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, - Account owner, Long deviceId, boolean incrementResourceCount); + Account owner, Long deviceId, Long kmsKeyId, boolean incrementResourceCount); - VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException; + VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volume, HypervisorType rootDiskHyperType, StoragePool storagePool, Long clusterId, Long podId) throws NoTransitionException; void release(VirtualMachineProfile profile); @@ -137,7 +150,7 @@ DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Lon * Allocate a volume or multiple volumes in case of template is registered with the 'deploy-as-is' option, allowing multiple disks */ List allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, - Account owner); + Account owner, Long kmsKeyId, Volume volume, Snapshot snapshot); String getVmNameFromVolumeId(long volumeId); @@ -170,13 +183,18 @@ List allocateTemplatedVolumes(Type type, String name, DiskOffering */ DiskProfile importVolume(Type type, String name, DiskOffering offering, Long sizeInBytes, Long minIops, Long maxIops, Long zoneId, HypervisorType hypervisorType, VirtualMachine vm, VirtualMachineTemplate template, - Account owner, Long deviceId, Long poolId, String path, String chainInfo); + Account owner, Long deviceId, Long poolId, Storage.StoragePoolType poolType, String path, String chainInfo); DiskProfile updateImportedVolume(Type type, DiskOffering offering, VirtualMachine vm, VirtualMachineTemplate template, - Long deviceId, Long poolId, String path, String chainInfo, DiskProfile diskProfile); + Long deviceId, Long poolId, Storage.StoragePoolType poolType, String path, String chainInfo, DiskProfile diskProfile); /** * Unmanage VM volumes */ void unmanageVolumes(long vmId); + + /** + * Retrieves the volume's checkpoints paths to be used in the KVM processor. If there are no checkpoints, it will return an empty list. + */ + Pair, Set> getVolumeCheckpointPathsAndImageStoreUrls(long volumeId, HypervisorType hypervisorType); } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java index 3ffa496b5445..887aeaef0736 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/service/api/OrchestrationService.java @@ -18,6 +18,8 @@ */ package org.apache.cloudstack.engine.service.api; +import com.cloud.storage.Snapshot; +import com.cloud.storage.Volume; import java.net.URL; import java.util.List; import java.util.Map; @@ -38,6 +40,7 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.offering.DiskOffering; import com.cloud.vm.NicProfile; +import com.cloud.vm.VmDiskInfo; @Path("orchestration") @Produces({"application/json", "application/xml"}) @@ -62,20 +65,22 @@ public interface OrchestrationService { @POST @Path("/createvm") VirtualMachineEntity createVirtualMachine(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("template-id") String templateId, - @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, - @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, - @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, - @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, - @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId, @QueryParam("root-disk-offering-id") Long rootDiskOfferingId) throws InsufficientCapacityException; + @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, + @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, + @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, + @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, + @QueryParam("root-disk-size") Long rootDiskSize, @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, + @QueryParam("datadisktemplate-diskoffering-map") Map datadiskTemplateToDiskOfferingMap, @QueryParam("disk-offering-id") Long diskOfferingId, + @QueryParam("root-disk-offering-id") Long rootDiskOfferingId, @QueryParam("root-disk-kms-key-id") Long rootDiskKmsKeyId, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; @POST VirtualMachineEntity createVirtualMachineFromScratch(@QueryParam("id") String id, @QueryParam("owner") String owner, @QueryParam("iso-id") String isoId, - @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, - @QueryParam("os") String os, @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, - @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, - @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, - @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId) throws InsufficientCapacityException; + @QueryParam("host-name") String hostName, @QueryParam("display-name") String displayName, @QueryParam("hypervisor") String hypervisor, + @QueryParam("os") String os, @QueryParam("cpu") int cpu, @QueryParam("speed") int speed, @QueryParam("ram") long memory, @QueryParam("disk-size") Long diskSize, + @QueryParam("compute-tags") List computeTags, @QueryParam("root-disk-tags") List rootDiskTags, + @QueryParam("network-nic-map") Map> networkNicMap, @QueryParam("deploymentplan") DeploymentPlan plan, + @QueryParam("extra-dhcp-option-map") Map> extraDhcpOptionMap, @QueryParam("disk-offering-id") Long diskOfferingId, + @QueryParam("root-disk-kms-key-id") Long rootDiskKmsKeyId, @QueryParam("data-disks-offering-info") List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException; @POST NetworkEntity createNetwork(String id, String name, String domainName, String cidr, String gateway); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ClusterScope.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ClusterScope.java index b0ed7d7d52b5..68c7ed40d8f4 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ClusterScope.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ClusterScope.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage; import com.cloud.storage.ScopeType; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; public class ClusterScope extends AbstractScope { private ScopeType type = ScopeType.CLUSTER; @@ -51,4 +52,9 @@ public Long getZoneId() { return this.zoneId; } + @Override + public String toString() { + return String.format("ClusterScope %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "zoneId", "clusterId", "podId")); + } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java index f537d8f52028..2494cc7c5fcb 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreCapabilities.java @@ -40,5 +40,13 @@ public enum DataStoreCapabilities { /** * indicates that this driver supports reverting a volume to a snapshot state */ - CAN_REVERT_VOLUME_TO_SNAPSHOT + CAN_REVERT_VOLUME_TO_SNAPSHOT, + /** + * indicates that the driver supports copying snapshot between zones on pools of the same type + */ + CAN_COPY_SNAPSHOT_BETWEEN_ZONES_AND_SAME_POOL_TYPE, + /** + * indicates that this driver supports the option to create a template from the back-end snapshot + */ + CAN_CREATE_TEMPLATE_FROM_SNAPSHOT } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java index b197afad863a..af3b38b368c3 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreDriver.java @@ -45,4 +45,8 @@ public interface DataStoreDriver { boolean canCopy(DataObject srcData, DataObject destData); void resize(DataObject data, AsyncCompletionCallback callback); + + default boolean canDisplayDetails() { + return true; + } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProvider.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProvider.java index 41c1d9407454..7ff206db3c99 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProvider.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreProvider.java @@ -29,6 +29,11 @@ public interface DataStoreProvider { String SAMPLE_IMAGE = "Sample"; String SMB = "NFS"; String DEFAULT_PRIMARY = "DefaultPrimary"; + /** + * Primary storage provider name for NetApp ONTAP ({@code OntapPrimaryDatastoreProvider#getName}). + * Single canonical value; use this instead of duplicating the string across modules. + */ + String ONTAP_PLUGIN_NAME = "NetApp ONTAP"; enum DataStoreProviderType { PRIMARY, IMAGE, ImageCache, OBJECT diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java index 4f2a69bc7719..7cf2a593db4a 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/EndPointSelector.java @@ -18,6 +18,8 @@ */ package org.apache.cloudstack.engine.subsystem.api.storage; +import com.cloud.hypervisor.Hypervisor; + import java.util.List; public interface EndPointSelector { @@ -39,6 +41,8 @@ public interface EndPointSelector { EndPoint select(DataObject object, StorageAction action, boolean encryptionSupportRequired); + EndPoint selectRandom(long zoneId, Hypervisor.HypervisorType hypervisorType); + List selectAll(DataStore store); List findAllEndpointsForScope(DataStore store); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/HostScope.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/HostScope.java index 6e0bc618bfe2..7e6ffe7d4f74 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/HostScope.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/HostScope.java @@ -19,8 +19,10 @@ package org.apache.cloudstack.engine.subsystem.api.storage; import com.cloud.storage.ScopeType; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; public class HostScope extends AbstractScope { + private ScopeType type = ScopeType.HOST; private Long hostId; private Long clusterId; private Long zoneId; @@ -34,7 +36,7 @@ public HostScope(Long hostId, Long clusterId, Long zoneId) { @Override public ScopeType getScopeType() { - return ScopeType.HOST; + return this.type; } @Override @@ -49,4 +51,10 @@ public Long getClusterId() { public Long getZoneId() { return zoneId; } + + @Override + public String toString() { + return String.format("HostScope %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "zoneId", "clusterId", "hostId")); + } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectInDataStoreStateMachine.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectInDataStoreStateMachine.java index d4ca163e4e6f..afa5d4e36db8 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectInDataStoreStateMachine.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ObjectInDataStoreStateMachine.java @@ -32,7 +32,9 @@ enum State { Migrated("The object has been migrated"), Destroying("Template is destroying"), Destroyed("Template is destroyed"), - Failed("Failed to download Template"); + Failed("Failed to download Template"), + Hidden("The object is hidden from the user"); + final String _description; State(String description) { diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java index c8d9015af902..d908ef6af508 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreDriver.java @@ -145,6 +145,14 @@ default boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool) { return false; } + /** + * intended for managed storage + * returns true if the host can be disconnected from storage pool + */ + default boolean canDisconnectHostFromStoragePool(Host host, StoragePool pool) { + return true; + } + /** * Used by storage pools which want to keep VMs' information * @return true if additional VM info is needed (intended for storage pools). diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java index 54f3c63f8d73..1acaccf09df4 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreLifeCycle.java @@ -24,8 +24,8 @@ import com.cloud.storage.StoragePool; public interface PrimaryDataStoreLifeCycle extends DataStoreLifeCycle { - public static final String CAPACITY_BYTES = "capacityBytes"; - public static final String CAPACITY_IOPS = "capacityIops"; + String CAPACITY_BYTES = "capacityBytes"; + String CAPACITY_IOPS = "capacityIops"; void updateStoragePool(StoragePool storagePool, Map details); void enableStoragePool(DataStore store); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java index 1b18264df15d..adb77e69e90e 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/PrimaryDataStoreParameters.java @@ -30,6 +30,7 @@ public class PrimaryDataStoreParameters { private String providerName; private Map details; private String tags; + private String storageAccessGroups; private StoragePoolType type; private HypervisorType hypervisorType; private String host; @@ -165,6 +166,21 @@ public void setTags(String tags) { this.tags = tags; } + /** + * @return the storageAccessGroups + */ + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + /** + * @param storageAccessGroups + * the storageAccessGroups to set + */ + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + /** * @return the details */ diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotInfo.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotInfo.java index 9a05745fb2d7..997a66ec5901 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotInfo.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotInfo.java @@ -32,6 +32,10 @@ public interface SnapshotInfo extends DataObject, Snapshot { String getPath(); + DataStore getImageStore(); + + void setImageStore(DataStore imageStore); + SnapshotInfo getChild(); List getChildren(); @@ -56,6 +60,14 @@ public interface SnapshotInfo extends DataObject, Snapshot { void markBackedUp() throws CloudRuntimeException; + String getCheckpointPath(); + + void setCheckpointPath(String checkpointPath); + + void setKvmIncrementalSnapshot(boolean isKvmIncrementalSnapshot); + + boolean isKvmIncrementalSnapshot(); + Snapshot getSnapshotVO(); long getAccountId(); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java index d2e085fe90cf..18c924167e0b 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotService.java @@ -25,8 +25,12 @@ public interface SnapshotService { SnapshotResult takeSnapshot(SnapshotInfo snapshot); + DataStore findSnapshotImageStore(SnapshotInfo snapshot); + SnapshotInfo backupSnapshot(SnapshotInfo snapshot); + SnapshotInfo convertSnapshot(SnapshotInfo snapshotInfo); + boolean deleteSnapshot(SnapshotInfo snapshot); boolean revertSnapshot(SnapshotInfo snapshot); @@ -42,4 +46,6 @@ public interface SnapshotService { AsyncCallFuture copySnapshot(SnapshotInfo snapshot, String copyUrl, DataStore dataStore) throws ResourceUnavailableException; AsyncCallFuture queryCopySnapshot(SnapshotInfo snapshot) throws ResourceUnavailableException; + + AsyncCallFuture copySnapshot(SnapshotInfo sourceSnapshot, SnapshotInfo destSnapshot, SnapshotStrategy strategy); } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java index f3aa8f52c932..43f411f75533 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/SnapshotStrategy.java @@ -16,12 +16,14 @@ // under the License. package org.apache.cloudstack.engine.subsystem.api.storage; + import com.cloud.storage.Snapshot; +import org.apache.cloudstack.framework.async.AsyncCompletionCallback; public interface SnapshotStrategy { enum SnapshotOperation { - TAKE, BACKUP, DELETE, REVERT + TAKE, BACKUP, DELETE, REVERT, COPY } SnapshotInfo takeSnapshot(SnapshotInfo snapshot); @@ -35,4 +37,7 @@ enum SnapshotOperation { StrategyPriority canHandle(Snapshot snapshot, Long zoneId, SnapshotOperation op); void postSnapshotCreation(SnapshotInfo snapshot); + + default void copySnapshot(DataObject snapshotSource, DataObject snapshotDest, AsyncCompletionCallback caller) { + } } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java index dc93f4aa41e1..037676f81899 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StorageAction.java @@ -22,6 +22,8 @@ public enum StorageAction { TAKESNAPSHOT, BACKUPSNAPSHOT, DELETESNAPSHOT, + CONVERTSNAPSHOT, + REMOVEBITMAP, MIGRATEVOLUME, DELETEVOLUME } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StoragePoolAllocator.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StoragePoolAllocator.java index 6a78f6fe253f..9a2dc7346859 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StoragePoolAllocator.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/StoragePoolAllocator.java @@ -64,4 +64,5 @@ public interface StoragePoolAllocator extends Adapter { static int RETURN_UPTO_ALL = -1; List reorderPools(List pools, VirtualMachineProfile vmProfile, DeploymentPlan plan, DiskProfile dskCh); + } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java index 269eb4f1c213..2f8d57171bc9 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java @@ -67,6 +67,12 @@ public TemplateInfo getTemplate() { void handleTemplateSync(DataStore store); + void enforceSecStorageCopyLimit(long templateId, long zoneId); + + boolean canCopyTemplateToImageStore(long templateId, long zoneId); + + void replicateTemplateUpToCap(long templateId, long zoneId); + void downloadBootstrapSysTemplate(DataStore store); void addSystemVMTemplatesToSecondary(DataStore store); diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java index 19b852bd9126..4937edd33d1f 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeInfo.java @@ -26,6 +26,9 @@ import com.cloud.storage.Volume; import com.cloud.vm.VirtualMachine; +import java.util.List; +import java.util.Set; + public interface VolumeInfo extends DownloadableDataInfo, Volume { boolean isAttachedVM(); @@ -96,4 +99,25 @@ public interface VolumeInfo extends DownloadableDataInfo, Volume { public byte[] getPassphrase(); Volume getVolume(); + + List getCheckpointPaths(); + + Set getCheckpointImageStoreUrls(); + + /** + * Gets the destination host ID hint for CLVM volume creation. + * This is used to route volume creation commands to the specific host where the VM will be deployed. + * Only applicable for CLVM storage pools to avoid shared mode activation. + * + * @return The host ID where the volume should be created, or null if not set + */ + Long getDestinationHostId(); + + /** + * Sets the destination host ID hint for CLVM volume creation. + * This should be set before volume creation when the destination host is known. + * + * @param hostId The host ID where the volume should be created + */ + void setDestinationHostId(Long hostId); } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java index 682473ec94fc..a7d82d0b9628 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/VolumeService.java @@ -30,6 +30,7 @@ import com.cloud.host.Host; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.offering.DiskOffering; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.storage.Volume; import com.cloud.user.Account; import com.cloud.utils.Pair; @@ -123,4 +124,71 @@ boolean copyPoliciesBetweenVolumesAndDestroySourceVolumeAfterMigration(ObjectInD void checkAndRepairVolumeBasedOnConfig(DataObject dataObject, Host host); void validateChangeDiskOfferingEncryptionType(long existingDiskOfferingId, long newDiskOfferingId); + + /** + * Transfers exclusive lock for a volume on cluster-based storage (e.g., CLVM/CLVM_NG) from one host to another. + * This is used for storage that requires host-level lock management for volumes on shared storage pools. + * For non-CLVM pool types, this method returns false without taking action. + * + * @param volume The volume to transfer lock for + * @param sourceHostId Host currently holding the exclusive lock + * @param destHostId Host to receive the exclusive lock + * @return true if lock transfer succeeded or was not needed, false if it failed + */ + boolean transferVolumeLock(VolumeInfo volume, Long sourceHostId, Long destHostId); + + /** + * Finds which host currently has the exclusive lock on a CLVM volume. + * Checks in order: explicit lock tracking, attached VM's host, or first available cluster host. + * + * @param volume The CLVM volume + * @return Host ID that has the exclusive lock, or null if cannot be determined + */ + Long findVolumeLockHost(VolumeInfo volume); + + /** + * Performs lightweight CLVM lock migration for a volume to a target host. + * This transfers the LVM exclusive lock without copying data (CLVM volumes are on shared cluster storage). + * If the volume already has the lock on the destination host, no action is taken. + * + * @param volume The volume to migrate lock for + * @param destHostId Destination host ID + * @return Updated VolumeInfo after lock migration + */ + VolumeInfo performLockMigration(VolumeInfo volume, Long destHostId); + + /** + * Checks if both storage pools are CLVM type (CLVM or CLVM_NG). + * + * @param volumePoolType Storage pool type for the volume + * @param vmPoolType Storage pool type for the VM + * @return true if both pools are CLVM type (CLVM or CLVM_NG) + */ + boolean areBothPoolsClvmType(StoragePoolType volumePoolType, StoragePoolType vmPoolType); + + /** + * Determines if CLVM lock transfer is required when a volume is already on the correct storage pool. + * + * @param volumeToAttach The volume being attached + * @param volumePoolType Storage pool type for the volume + * @param vmPoolType Storage pool type for the VM's existing volume + * @param volumePoolId Storage pool ID for the volume + * @param vmPoolId Storage pool ID for the VM's existing volume + * @param vmHostId VM's current host ID (or last host ID if stopped) + * @return true if CLVM lock transfer is needed + */ + boolean isLockTransferRequired(VolumeInfo volumeToAttach, StoragePoolType volumePoolType, StoragePoolType vmPoolType, + Long volumePoolId, Long vmPoolId, Long vmHostId); + + /** + * Determines if lightweight CLVM migration is needed instead of full data copy. + * + * @param volumePoolType Storage pool type for the volume + * @param vmPoolType Storage pool type for the VM + * @param volumePoolPath Storage pool path for the volume + * @param vmPoolPath Storage pool path for the VM + * @return true if lightweight migration should be used + */ + boolean isLightweightMigrationNeeded(StoragePoolType volumePoolType, StoragePoolType vmPoolType, + String volumePoolPath, String vmPoolPath); } diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ZoneScope.java b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ZoneScope.java index a0d75b5cc7c9..fa704d05b1ae 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ZoneScope.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/ZoneScope.java @@ -19,6 +19,7 @@ package org.apache.cloudstack.engine.subsystem.api.storage; import com.cloud.storage.ScopeType; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; public class ZoneScope extends AbstractScope { private ScopeType type = ScopeType.ZONE; @@ -39,4 +40,9 @@ public Long getScopeId() { return this.zoneId; } + @Override + public String toString() { + return String.format("ZoneScope %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "zoneId")); + } } diff --git a/engine/components-api/pom.xml b/engine/components-api/pom.xml index 395f7174834d..49d41d36f83d 100644 --- a/engine/components-api/pom.xml +++ b/engine/components-api/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java b/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java index f70ab494fdc1..4d63fae33560 100644 --- a/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java +++ b/engine/components-api/src/main/java/com/cloud/agent/AgentManager.java @@ -175,5 +175,7 @@ enum TapAgentsAction { void propagateChangeToAgents(Map params); + boolean transferDirectAgentsFromMS(String fromMsUuid, long fromMsId, long timeoutDurationInMs, boolean excludeHostsInMaintenance); + int getHostSshPort(HostVO host); } diff --git a/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java b/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java index 7fe19c3ba9f8..46993b066a49 100644 --- a/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java +++ b/engine/components-api/src/main/java/com/cloud/alert/AlertManager.java @@ -49,6 +49,10 @@ public interface AlertManager extends Manager, AlertService { "Percentage (as a value between 0 and 1) of guest network IPv6 subnet utilization above which alerts will be sent.", true); + ConfigKey AllowedRepetitiveAlertTypes = new ConfigKey<>(ConfigKey.CATEGORY_ALERT, String.class, + "alert.allowed.repetitive.types", "", + "Comma-separated list of alert types (by name) that can be sent multiple times", true); + void clearAlert(AlertType alertType, long dataCenterId, long podId); void recalculateCapacity(); 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 c3d45b98b00b..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 @@ -16,6 +16,8 @@ // under the License. package com.cloud.capacity; +import java.util.List; + import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; @@ -67,7 +69,7 @@ public interface CapacityManager { "0.85", "Percentage (as a value between 0 and 1) of storage utilization above which allocators will disable using the pool for low storage available.", true, - ConfigKey.Scope.Zone); + List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone)); static final ConfigKey StorageOverprovisioningFactor = new ConfigKey<>( "Storage", @@ -85,7 +87,7 @@ public interface CapacityManager { "0.85", "Percentage (as a value between 0 and 1) of allocated storage utilization above which allocators will disable using the pool for low allocated storage available.", true, - ConfigKey.Scope.Zone); + List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone)); static final ConfigKey StorageOperationsExcludeCluster = new ConfigKey<>( Boolean.class, @@ -125,12 +127,26 @@ public interface CapacityManager { "Percentage (as a value between 0 and 1) of allocated storage utilization above which allocators will disable using the pool for volume resize. " + "This is applicable only when volume.resize.allowed.beyond.allocation is set to true.", true, - ConfigKey.Scope.Zone); + List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone)); ConfigKey CapacityCalculateWorkers = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Integer.class, "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/configuration/ConfigurationManager.java b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java index 1694b19c33fd..5909d098db8b 100644 --- a/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java +++ b/engine/components-api/src/main/java/com/cloud/configuration/ConfigurationManager.java @@ -56,18 +56,25 @@ */ public interface ConfigurationManager { - public static final String MESSAGE_CREATE_POD_IP_RANGE_EVENT = "Message.CreatePodIpRange.Event"; - public static final String MESSAGE_DELETE_POD_IP_RANGE_EVENT = "Message.DeletePodIpRange.Event"; - public static final String MESSAGE_CREATE_VLAN_IP_RANGE_EVENT = "Message.CreateVlanIpRange.Event"; - public static final String MESSAGE_DELETE_VLAN_IP_RANGE_EVENT = "Message.DeleteVlanIpRange.Event"; + String MESSAGE_CREATE_POD_IP_RANGE_EVENT = "Message.CreatePodIpRange.Event"; + String MESSAGE_DELETE_POD_IP_RANGE_EVENT = "Message.DeletePodIpRange.Event"; + String MESSAGE_CREATE_VLAN_IP_RANGE_EVENT = "Message.CreateVlanIpRange.Event"; + String MESSAGE_DELETE_VLAN_IP_RANGE_EVENT = "Message.DeleteVlanIpRange.Event"; public static final ConfigKey AllowNonRFC1918CompliantIPs = new ConfigKey<>(Boolean.class, "allow.non.rfc1918.compliant.ips", "Advanced", "false", "Allows non-compliant RFC 1918 IPs for Shared, Isolated networks and VPCs", true, null); + ConfigKey HostCapacityTypeCpuMemoryWeight = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Float.class, + "host.capacityType.to.order.clusters.cputomemoryweight", + "0.5", + "Weight for CPU (as a value between 0 and 1) applied to compute capacity for Pods, Clusters and Hosts for COMBINED capacityType for ordering. Weight for RAM will be (1 - weight of CPU)", + true, ConfigKey.Scope.Global); + /** - * @param offering - * @return + * Is this for a VPC + * @param offering the offering to check + * @return true or false */ boolean isOfferingForVpc(NetworkOffering offering); @@ -77,86 +84,19 @@ public interface ConfigurationManager { /** * Updates a configuration entry with a new value - * - * @param userId - * @param name - * @param value */ - String updateConfiguration(long userId, String name, String category, String value, String scope, Long id); - -// /** -// * Creates a new service offering -// * -// * @param name -// * @param cpu -// * @param ramSize -// * @param speed -// * @param displayText -// * @param localStorageRequired -// * @param offerHA -// * @param domainId -// * @param volatileVm -// * @param hostTag -// * @param networkRate -// * @param id -// * @param useVirtualNetwork -// * @param deploymentPlanner -// * @param details -// * @param bytesReadRate -// * @param bytesWriteRate -// * @param iopsReadRate -// * @param iopsWriteRate -// * @return ID -// */ -// ServiceOfferingVO createServiceOffering(long userId, boolean isSystem, VirtualMachine.Type vm_typeType, String name, int cpu, int ramSize, int speed, String displayText, boolean localStorageRequired, -// boolean offerHA, boolean limitResourceUse, boolean volatileVm, String tags, Long domainId, String hostTag, Integer networkRate, String deploymentPlanner, Map details, -// Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate); - -// /** -// * Creates a new disk offering -// * -// * @param domainId -// * @param name -// * @param description -// * @param numGibibytes -// * @param tags -// * @param isCustomized -// * @param localStorageRequired -// * @param isDisplayOfferingEnabled -// * @param isCustomizedIops (is admin allowing users to set custom iops?) -// * @param minIops -// * @param maxIops -// * @param bytesReadRate -// * @param bytesWriteRate -// * @param iopsReadRate -// * @param iopsWriteRate -// * @return newly created disk offering -// */ -// DiskOfferingVO createDiskOffering(Long domainId, String name, String description, Long numGibibytes, String tags, boolean isCustomized, -// boolean localStorageRequired, boolean isDisplayOfferingEnabled, Boolean isCustomizedIops, Long minIops, Long maxIops, -// Long bytesReadRate, Long bytesWriteRate, Long iopsReadRate, Long iopsWriteRate); + String updateConfiguration(long userId, String name, String category, String value, ConfigKey.Scope scope, Long id); /** * Creates a new pod - * - * @param userId - * @param podName - * @param zone - * @param gateway - * @param cidr - * @param startIp - * @param endIp - * @param allocationState - * @param skipGatewayOverlapCheck - * (true if it is ok to not validate that gateway IP address overlap with Start/End IP of the POD) + * @param skipGatewayOverlapCheck (true if it is ok to not validate that gateway IP address overlap with Start/End IP of the POD) * @return Pod */ HostPodVO createPod(long userId, String podName, DataCenter zone, String gateway, String cidr, String startIp, String endIp, String allocationState, - boolean skipGatewayOverlapCheck); + boolean skipGatewayOverlapCheck, List storageAccessGroups); /** * Creates a new zone - * * @param userId * @param zoneName * @param dns1 @@ -164,27 +104,28 @@ HostPodVO createPod(long userId, String podName, DataCenter zone, String gateway * @param internalDns1 * @param internalDns2 * @param guestCidr + * @param domain + * @param domainId * @param zoneType * @param allocationState * @param networkDomain * @param isSecurityGroupEnabled + * @param isLocalStorageEnabled * @param ip6Dns1 * @param ip6Dns2 + * @param isEdge + * @param storageAccessGroups * @return - * @throws - * @throws */ DataCenterVO createZone(long userId, String zoneName, String dns1, String dns2, String internalDns1, String internalDns2, String guestCidr, String domain, Long domainId, NetworkType zoneType, String allocationState, String networkDomain, boolean isSecurityGroupEnabled, boolean isLocalStorageEnabled, String ip6Dns1, - String ip6Dns2, boolean isEdge); + String ip6Dns2, boolean isEdge, List storageAccessGroups); /** * Deletes a VLAN from the database, along with all of its IP addresses. Will not delete VLANs that have allocated * IP addresses. * - * @param userId - * @param vlanDbId - * @param caller + * @param caller TODO * @return success/failure */ VlanVO deleteVlanAndPublicIpRange(long userId, long vlanDbId, Account caller); @@ -195,39 +136,48 @@ DataCenterVO createZone(long userId, String zoneName, String dns1, String dns2, /** * Creates a new network offering - * * @param name * @param displayText * @param trafficType * @param tags * @param specifyVlan + * @param availability * @param networkRate * @param serviceProviderMap * @param isDefault * @param type * @param systemOnly * @param serviceOfferingId - * @param conserveMode ; + * @param conserveMode + * @param serviceCapabilityMap * @param specifyIpRanges - * @param isPersistent ; + * @param isPersistent * @param details + * @param egressDefaultPolicy + * @param maxconn + * @param enableKeepAlive * @param forVpc * @param forTungsten * @param forNsx + * @param forNetris + * @param networkMode * @param domainIds * @param zoneIds - * @return network offering object + * @param enableOffering + * @param internetProtocol + * @param routingMode + * @param specifyAsNumber + * @return the network offering */ - NetworkOfferingVO createNetworkOffering(String name, String displayText, TrafficType trafficType, String tags, boolean specifyVlan, Availability availability, Integer networkRate, Map> serviceProviderMap, boolean isDefault, Network.GuestType type, boolean systemOnly, Long serviceOfferingId, boolean conserveMode, Map> serviceCapabilityMap, boolean specifyIpRanges, boolean isPersistent, Map details, boolean egressDefaultPolicy, Integer maxconn, boolean enableKeepAlive, Boolean forVpc, - Boolean forTungsten, boolean forNsx, NetworkOffering.NetworkMode networkMode, List domainIds, List zoneIds, boolean enableOffering, final NetUtils.InternetProtocol internetProtocol, + Boolean forTungsten, boolean forNsx, boolean forNetris, NetworkOffering.NetworkMode networkMode, List domainIds, List zoneIds, boolean enableOffering, final NetUtils.InternetProtocol internetProtocol, NetworkOffering.RoutingMode routingMode, boolean specifyAsNumber); Vlan createVlanAndPublicIpRange(long zoneId, long networkId, long physicalNetworkId, boolean forVirtualNetwork, boolean forSystemVms, Long podId, String startIP, String endIP, - String vlanGateway, String vlanNetmask, String vlanId, boolean bypassVlanOverlapCheck, Domain domain, Account vlanOwner, String startIPv6, String endIPv6, String vlanIp6Gateway, String vlanIp6Cidr, boolean forNsx) + String vlanGateway, String vlanNetmask, String vlanId, boolean bypassVlanOverlapCheck, Domain domain, Account vlanOwner, String startIPv6, String endIPv6, String vlanIp6Gateway, String vlanIp6Cidr, Provider provider) throws InsufficientCapacityException, ConcurrentOperationException, InvalidParameterValueException; void createDefaultSystemNetworks(long zoneId) throws ConcurrentOperationException; @@ -235,7 +185,6 @@ Vlan createVlanAndPublicIpRange(long zoneId, long networkId, long physicalNetwor /** * Release dedicated virtual ip ranges of a domain. * - * @param domainId * @return success/failure */ boolean releaseDomainSpecificVirtualRanges(Domain domain); @@ -243,7 +192,6 @@ Vlan createVlanAndPublicIpRange(long zoneId, long networkId, long physicalNetwor /** * Release dedicated virtual ip ranges of an account. * - * @param accountId * @return success/failure */ boolean releaseAccountSpecificVirtualRanges(Account account); @@ -251,16 +199,7 @@ Vlan createVlanAndPublicIpRange(long zoneId, long networkId, long physicalNetwor /** * Edits a pod in the database. Will not allow you to edit pods that are being used anywhere in the system. * - * @param id - * @param name - * @param startIp - * @param endIp - * @param gateway - * @param netmask - * @param allocationState * @return Pod - * @throws - * @throws */ Pod editPod(long id, String name, String startIp, String endIp, String gateway, String netmask, String allocationState); diff --git a/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java b/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java index 94fbb7a80af8..1c88c7df124c 100644 --- a/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java +++ b/engine/components-api/src/main/java/com/cloud/event/UsageEventUtils.java @@ -94,6 +94,14 @@ public static void publishUsageEvent(String usageType, long accountId, long zone } + public static void publishUsageEvent(String usageType, long accountId, long zoneId, long resourceId, String resourceName, Long offeringId, Long templateId, + Long size, String entityType, String entityUUID, Long vmId, boolean displayResource) { + if (displayResource) { + saveUsageEvent(usageType, accountId, zoneId, resourceId, offeringId, templateId, size, vmId, resourceName); + } + publishUsageEvent(usageType, accountId, zoneId, entityType, entityUUID); + } + public static void publishUsageEvent(String usageType, long accountId, long zoneId, long resourceId, String resourceName, Long offeringId, Long templateId, Long size, Long virtualSize, String entityType, String entityUUID, Map details) { saveUsageEvent(usageType, accountId, zoneId, resourceId, resourceName, offeringId, templateId, size, virtualSize, details); @@ -202,6 +210,10 @@ public static void saveUsageEvent(String usageType, long accountId, long zoneId, s_usageEventDao.persist(new UsageEventVO(usageType, accountId, zoneId, vmId, securityGroupId)); } + public static void saveUsageEvent(String usageType, long accountId, long zoneId, long resourceId, Long offeringId, Long templateId, Long size, Long vmId, String resourceName) { + s_usageEventDao.persist(new UsageEventVO(usageType, accountId, zoneId, resourceId, offeringId, templateId, size, vmId, resourceName)); + } + private static void publishUsageEvent(String usageEventType, Long accountId, Long zoneId, String resourceType, String resourceUUID) { String configKey = "publish.usage.events"; String value = s_configDao.getValue(configKey); 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 ddc8153d7398..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 @@ -21,6 +21,7 @@ import com.cloud.deploy.DeploymentPlanner; import com.cloud.host.HostVO; import com.cloud.host.Status; +import com.cloud.storage.Storage.StoragePoolType; import com.cloud.utils.component.Manager; import com.cloud.vm.VMInstanceVO; import org.apache.cloudstack.framework.config.ConfigKey; @@ -32,6 +33,8 @@ */ public interface HighAvailabilityManager extends Manager { + List LIBVIRT_STORAGE_POOL_TYPES_WITH_HA_SUPPORT = List.of(StoragePoolType.NetworkFilesystem, StoragePoolType.SharedMountPoint); + ConfigKey ForceHA = new ConfigKey<>("Advanced", Boolean.class, "force.ha", "false", "Force High-Availability to happen even if the VM says no.", true, Cluster); @@ -72,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/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java b/engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java new file mode 100644 index 000000000000..c574a8be0175 --- /dev/null +++ b/engine/components-api/src/main/java/com/cloud/hypervisor/ExternalProvisioner.java @@ -0,0 +1,65 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.hypervisor; + +import java.util.Map; + +import com.cloud.agent.api.GetExternalConsoleAnswer; +import com.cloud.agent.api.GetExternalConsoleCommand; +import com.cloud.agent.api.HostVmStateReportEntry; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; +import com.cloud.agent.api.RebootAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.RunCustomActionAnswer; +import com.cloud.agent.api.RunCustomActionCommand; +import com.cloud.agent.api.StartAnswer; +import com.cloud.agent.api.StartCommand; +import com.cloud.agent.api.StopAnswer; +import com.cloud.agent.api.StopCommand; +import com.cloud.utils.component.Manager; + +public interface ExternalProvisioner extends Manager { + + String getExtensionsPath(); + + String getExtensionPath(String relativePath); + + String getChecksumForExtensionPath(String extensionName, String relativePath); + + void prepareExtensionPath(String extensionName, boolean userDefined, String extensionRelativePath); + + void cleanupExtensionPath(String extensionName, String extensionRelativePath); + + void cleanupExtensionData(String extensionName, int olderThanDays, boolean cleanupDirectory); + + PrepareExternalProvisioningAnswer prepareExternalProvisioning(String hostGuid, String extensionName, String extensionRelativePath, PrepareExternalProvisioningCommand cmd); + + StartAnswer startInstance(String hostGuid, String extensionName, String extensionRelativePath, StartCommand cmd); + + StopAnswer stopInstance(String hostGuid, String extensionName, String extensionRelativePath, StopCommand cmd); + + RebootAnswer rebootInstance(String hostGuid, String extensionName, String extensionRelativePath, RebootCommand cmd); + + StopAnswer expungeInstance(String hostGuid, String extensionName, String extensionRelativePath, StopCommand cmd); + + Map getHostVmStateReport(long hostId, String extensionName, String extensionRelativePath); + + GetExternalConsoleAnswer getInstanceConsole(String hostGuid, String extensionName, String extensionRelativePath, GetExternalConsoleCommand cmd); + + RunCustomActionAnswer runCustomAction(String hostGuid, String extensionName, String extensionRelativePath, RunCustomActionCommand cmd); +} diff --git a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java index b1cad20b19ec..454cb10a2f2b 100644 --- a/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/IpAddressManager.java @@ -288,4 +288,6 @@ List listAvailablePublicIps(final long dcId, PublicIpQuarantine updatePublicIpAddressInQuarantine(Long quarantineProcessId, Date endDate); void updateSourceNatIpAddress(IPAddressVO requestedIp, List userIps) throws Exception; + + Long getPreferredNetworkIdForPublicIpRuleAssignment(IpAddress ip, Long networkId); } diff --git a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java index 2f9cd45d6328..54a99fdf3677 100644 --- a/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java +++ b/engine/components-api/src/main/java/com/cloud/network/addr/PublicIp.java @@ -278,4 +278,9 @@ public void setRuleState(State ruleState) { public boolean isForSystemVms() { return false; } + + @Override + public boolean isForRouter() { + return _addr.isForRouter(); + } } diff --git a/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java b/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java index 669456cbdcc2..d8011e9ade12 100644 --- a/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/lb/LoadBalancingRulesManager.java @@ -39,7 +39,7 @@ public interface LoadBalancingRulesManager { LoadBalancer createPublicLoadBalancer(String xId, String name, String description, int srcPort, int destPort, long sourceIpId, String protocol, String algorithm, - boolean openFirewall, CallContext caller, String lbProtocol, Boolean forDisplay, String cidrList) throws NetworkRuleConflictException; + boolean openFirewall, CallContext caller, String lbProtocol, Boolean forDisplay, String cidrList, Long networkId) throws NetworkRuleConflictException; boolean removeAllLoadBalanacersForIp(long ipId, Account caller, long callerUserId); diff --git a/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java b/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java index 79ffdfdb9737..5ba74402a4c6 100644 --- a/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/rules/RulesManager.java @@ -54,6 +54,8 @@ FirewallRule[] reservePorts(IpAddress ip, String protocol, FirewallRule.Purpose boolean disableStaticNat(long ipAddressId, Account caller, long callerUserId, boolean releaseIpIfElastic) throws ResourceUnavailableException; + boolean applyStaticNatForIp(long sourceIpId, boolean continueOnError, Account caller, boolean forRevoke); + /** * @param networkId * @param continueOnError diff --git a/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java b/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java index 4d8270ca078d..980606176024 100644 --- a/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java +++ b/engine/components-api/src/main/java/com/cloud/network/rules/StaticNatRuleImpl.java @@ -80,10 +80,15 @@ public long getDomainId() { } @Override - public long getNetworkId() { + public Long getNetworkId() { return networkId; } + @Override + public Long getVpcId() { + return null; + } + @Override public long getId() { return id; diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java index de69b8941838..b192cd8d71c7 100644 --- a/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/vpc/NetworkACLManager.java @@ -93,5 +93,5 @@ public interface NetworkACLManager { boolean applyACLToPrivateGw(PrivateGateway gateway) throws ResourceUnavailableException; - boolean reorderAclRules(VpcVO vpc, List networks, List networkACLItems); + boolean reorderAclRules(VpcVO vpc, List networks, List networkACLItems, Network.Provider networkProvider); } diff --git a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java index 15158b72fab8..792a3a6b397f 100644 --- a/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java +++ b/engine/components-api/src/main/java/com/cloud/network/vpc/VpcManager.java @@ -38,8 +38,26 @@ import com.cloud.network.addr.PublicIp; import com.cloud.offering.NetworkOffering; import com.cloud.user.Account; +import org.apache.cloudstack.framework.config.ConfigKey; public interface VpcManager { + ConfigKey VpcTierNamePrepend = new ConfigKey<>(Boolean.class, + "vpc.tier.name.prepend", + ConfigKey.CATEGORY_NETWORK, + "false", + "Whether to prepend the VPC name to the VPC tier network name", + true, + ConfigKey.Scope.Global, + null); + ConfigKey VpcTierNamePrependDelimiter = new ConfigKey<>(String.class, + "vpc.tier.name.prepend.delimiter", + ConfigKey.CATEGORY_NETWORK, + " ", + "Delimiter string to use between the VPC and the VPC tier name", + true, + ConfigKey.Scope.Global, + null); + /** * Returns all the Guest networks that are part of VPC * @@ -177,4 +195,25 @@ public interface VpcManager { * @return */ boolean isSrcNatIpRequired(long vpcOfferingId); + + boolean isSrcNatIpRequiredForVpcVr(long vpcOfferingId); + + List getVpcStaticRoutes(Long vpcId); + + List getVpcStaticRoutes(List routes); + + boolean isProviderSupportServiceInVpc(long vpcId, Service service, Provider provider); + + IPAddressVO getIpAddressForVpcVr(Vpc vpc, IPAddressVO ipAddress, boolean allocateIpIfNeeded); + + boolean configStaticNatForVpcVr(Vpc vpc, IPAddressVO ipAddress); + + void reconfigStaticNatForVpcVr(Long vpcId); + + boolean applyStaticRouteForVpcVpnIfNeeded(Long vpcId, boolean updateAllVpn) throws ResourceUnavailableException; + + /** + * Returns true if the network is part of a VPC, and the VPC is created from conserve mode enabled VPC offering + */ + boolean isNetworkOnVpcEnabledConserveMode(Network network); } diff --git a/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java b/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java index a2bb5945a9d1..60b0167758c7 100644 --- a/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java +++ b/engine/components-api/src/main/java/com/cloud/resource/Discoverer.java @@ -50,5 +50,4 @@ Map> find(long dcId, Long podId, L public void putParam(Map params); ServerResource reloadResource(HostVO host); - } diff --git a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java index 059578ab2ca1..4767e86e8ab8 100755 --- a/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java +++ b/engine/components-api/src/main/java/com/cloud/resource/ResourceManager.java @@ -21,6 +21,12 @@ import java.util.List; import java.util.Map; + +import com.cloud.offering.ServiceOffering; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; +import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreInfo; +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.vm.VirtualMachine; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; @@ -73,6 +79,11 @@ public interface ResourceManager extends ResourceService, Configurable { ConfigKey.Kind.Select, "," + CPU.CPUArch.getTypesAsCSV()); + ConfigKey SystemVMDefaultHypervisor = new ConfigKey(String.class, + "system.vm.default.hypervisor", "Advanced", "Any", "Hypervisor type used to create System VMs. Valid values are: XenServer, KVM, VMware, Hyperv, VirtualBox, " + + "Parralels, BareMetal, Ovm, LXC, Any", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, "XenServer, KVM, VMware, Hyperv, " + + "VirtualBox, Parralels, BareMetal, Ovm, LXC, Any"); + /** * Register a listener for different types of resource life cycle events. * There can only be one type of listener per type of host. @@ -97,6 +108,8 @@ public interface ResourceManager extends ResourceService, Configurable { public Host createHostAndAgent(Long hostId, ServerResource resource, Map details, boolean old, List hostTags, boolean forRebalance); + public Host createHostAndAgent(Long hostId, ServerResource resource, Map details, boolean old, List hostTags, boolean forRebalance, boolean isTransferredConnection); + public Host addHost(long zoneId, ServerResource resource, Type hostType, Map hostDetails); public HostVO createHostVOForConnectedAgent(StartupCommand[] cmds); @@ -109,6 +122,8 @@ public interface ResourceManager extends ResourceService, Configurable { public boolean executeUserRequest(long hostId, ResourceState.Event event) throws AgentUnavailableException; + boolean executeUserRequest(long hostId, ResourceState.Event event, boolean isForced, boolean isForceDeleteStorage) throws AgentUnavailableException; + boolean resourceStateTransitTo(Host host, Event event, long msId) throws NoTransitionException; boolean umanageHost(long hostId); @@ -192,29 +207,34 @@ public interface ResourceManager extends ResourceService, Configurable { */ boolean isHostGpuEnabled(long hostId); + boolean isGPUDeviceAvailable(ServiceOffering offering, Host host, Long vmId); + /** - * Check if host has GPU devices available - * @param hostId the host to be checked - * @param groupName: gpuCard name - * @param vgpuType the VGPU type - * @return true when the host has the capacity with given VGPU type + * Get available GPU device + * + * @param vm the vm for which GPU device is requested + * @param vgpuProfile the VGPU profile + * @param gpuCount + * @return GPUDeviceTO[] */ - boolean isGPUDeviceAvailable(Host host, String groupName, String vgpuType); + GPUDeviceTO getGPUDevice(VirtualMachine vm, long hostId, VgpuProfileVO vgpuProfile, int gpuCount); /** * Get available GPU device - * @param hostId the host to be checked - * @param groupName: gpuCard name - * @param vgpuType the VGPU type + * + * @param hostId the host to be checked + * @param groupName gpuCard name + * @param vgpuType the VGPU type * @return GPUDeviceTO[] */ GPUDeviceTO getGPUDevice(long hostId, String groupName, String vgpuType); /** * Return listof available GPU devices - * @param hostId, the host to be checked - * @param groupName: gpuCard name - * @param vgpuType the VGPU type + * + * @param hostId the host to be checked + * @param groupName gpuCard name + * @param vgpuType the VGPU type * @return List of HostGpuGroupsVO. */ List listAvailableGPUDevice(long hostId, String groupName, String vgpuType); @@ -226,6 +246,16 @@ public interface ResourceManager extends ResourceService, Configurable { */ void updateGPUDetails(long hostId, HashMap> groupDetails); + /** + * Update GPU device details (post VM deployment) + * + * @param vm the VirtualMachine object + * @param gpuDeviceTO GPU device details + */ + void updateGPUDetailsForVmStop(VirtualMachine vm, GPUDeviceTO gpuDeviceTO); + + void updateGPUDetailsForVmStart(long hostId, long vmId, GPUDeviceTO gpuDevice); + /** * Get GPU details for a host * @param host, the Host object @@ -236,4 +266,12 @@ public interface ResourceManager extends ResourceService, Configurable { HostVO findOneRandomRunningHostByHypervisor(HypervisorType type, Long dcId); boolean cancelMaintenance(final long hostId); + + void updateStoragePoolConnectionsOnHosts(Long poolId, List storageAccessGroups); + + List getEligibleUpHostsInClusterForStorageConnection(PrimaryDataStoreInfo primaryStore); + + List getEligibleUpAndEnabledHostsInClusterForStorageConnection(PrimaryDataStoreInfo primaryStore); + + List getEligibleUpAndEnabledHostsInZoneForStorageConnection(DataStore dataStore, long zoneId, HypervisorType hypervisorType); } diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index 4ce1f4a96385..032fcbe76dce 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -161,22 +161,22 @@ public interface StorageManager extends StorageService { ConfigKey.Scope.StoragePool, null); - ConfigKey PRIMARY_STORAGE_DOWNLOAD_WAIT = new ConfigKey("Storage", Integer.class, "primary.storage.download.wait", "10800", + ConfigKey PRIMARY_STORAGE_DOWNLOAD_WAIT = new ConfigKey<>("Storage", Integer.class, "primary.storage.download.wait", "10800", "In second, timeout for download template to primary storage", false); - ConfigKey SecStorageMaxMigrateSessions = new ConfigKey("Advanced", Integer.class, "secstorage.max.migrate.sessions", "2", + ConfigKey SecStorageMaxMigrateSessions = new ConfigKey<>("Advanced", Integer.class, "secstorage.max.migrate.sessions", "2", "The max number of concurrent copy command execution sessions that an SSVM can handle", false, ConfigKey.Scope.Global); - ConfigKey SecStorageVMAutoScaleDown = new ConfigKey("Advanced", Boolean.class, "secstorage.vm.auto.scale.down", "false", + ConfigKey SecStorageVMAutoScaleDown = new ConfigKey<>("Advanced", Boolean.class, "secstorage.vm.auto.scale.down", "false", "Setting this to 'true' will auto scale down SSVMs", true, ConfigKey.Scope.Global); - ConfigKey MaxDataMigrationWaitTime = new ConfigKey("Advanced", Integer.class, "max.data.migration.wait.time", "15", + ConfigKey MaxDataMigrationWaitTime = new ConfigKey<>("Advanced", Integer.class, "max.data.migration.wait.time", "15", "Maximum wait time (in minutes) for a data migration task before spawning a new SSVM", false, ConfigKey.Scope.Global); - ConfigKey DiskProvisioningStrictness = new ConfigKey("Storage", Boolean.class, "disk.provisioning.type.strictness", "false", + ConfigKey DiskProvisioningStrictness = new ConfigKey<>("Storage", Boolean.class, "disk.provisioning.type.strictness", "false", "If set to true, the disk is created only when there is a suitable storage pool that supports the disk provisioning type specified by the service/disk offering. " + "If set to false, the disk is created with a disk provisioning type supported by the pool. Default value is false, and this is currently supported for VMware only.", true, ConfigKey.Scope.Zone); - ConfigKey PreferredStoragePool = new ConfigKey(String.class, "preferred.storage.pool", "Advanced", "", + ConfigKey PreferredStoragePool = new ConfigKey<>(String.class, "preferred.storage.pool", "Advanced", "", "The UUID of preferred storage pool for allocation.", true, ConfigKey.Scope.Account, null); ConfigKey MountDisabledStoragePool = new ConfigKey<>(Boolean.class, @@ -195,6 +195,14 @@ public interface StorageManager extends StorageService { true, ConfigKey.Scope.StoragePool, null); + ConfigKey XenserverCreateCloneFull = new ConfigKey<>(Boolean.class, + "xenserver.create.full.clone", + "Storage", + "false", + "If set to true, creates VMs as full clones on XenServer hypervisor (uses VDI.copy instead of VDI.clone, removing the linked-clone parent relationship).", + true, + ConfigKey.Scope.StoragePool, + null); ConfigKey VmwareAllowParallelExecution = new ConfigKey<>(Boolean.class, "vmware.allow.parallel.command.execution", "Advanced", @@ -203,7 +211,7 @@ public interface StorageManager extends StorageService { true, ConfigKey.Scope.Global, null); - static final ConfigKey DataStoreDownloadFollowRedirects = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, + ConfigKey DataStoreDownloadFollowRedirects = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED, Boolean.class, "store.download.follow.redirects", "false", "Whether HTTP redirect is followed during store downloads for objects such as template, volume etc.", true, ConfigKey.Scope.Global); @@ -211,23 +219,34 @@ public interface StorageManager extends StorageService { ConfigKey HEURISTICS_SCRIPT_TIMEOUT = new ConfigKey<>("Advanced", Long.class, "heuristics.script.timeout", "3000", "The maximum runtime, in milliseconds, to execute the heuristic rule; if it is reached, a timeout will happen.", true); - ConfigKey AllowVolumeReSizeBeyondAllocation = new ConfigKey("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false", + ConfigKey AllowVolumeReSizeBeyondAllocation = new ConfigKey<>("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false", "Determines whether volume size can exceed the pool capacity allocation disable threshold (pool.storage.allocated.capacity.disablethreshold) " + "when resize a volume upto resize capacity disable threshold (pool.storage.allocated.resize.capacity.disablethreshold)", - true, ConfigKey.Scope.Zone); + true, List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone)); ConfigKey StoragePoolHostConnectWorkers = new ConfigKey<>("Storage", Integer.class, "storage.pool.host.connect.workers", "1", "Number of worker threads to be used to connect hosts to a primary storage", true); + ConfigKey ObjectStorageCapacityThreshold = new ConfigKey<>("Alert", Float.class, + "objectStorage.capacity.notificationthreshold", + "0.75", + "Percentage (as a value between 0 and 1) of object storage utilization above which alerts will be sent about low storage available.", + true, + ConfigKey.Scope.Global, + null); + ConfigKey COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES = new ConfigKey<>(Boolean.class, "copy.templates.from.other.secondary.storages", "Storage", "true", "When enabled, this feature allows templates to be copied from existing Secondary Storage servers (within the same zone or across zones) " + "while adding a new Secondary Storage. If the copy operation fails, the system falls back to downloading the template from the source URL.", true, ConfigKey.Scope.Zone, null); + ConfigKey AgentMaxDataMigrationWaitTime = new ConfigKey<>("Advanced", Integer.class, "agent.max.data.migration.wait.time", "3600", + "The maximum time (in seconds) that the secondary storage data migration command sent to the KVM Agent will be executed before a timeout occurs.", true, ConfigKey.Scope.Cluster); + /** * should we execute in sequence not involving any storages? - * @return tru if commands should execute in sequence + * @return true if commands should execute in sequence */ static boolean shouldExecuteInSequenceOnVmware() { return shouldExecuteInSequenceOnVmware(null, null); @@ -249,14 +268,14 @@ static Boolean getFullCloneConfiguration(Long storeId) { /** * Returns a comma separated list of tags for the specified storage pool - * @param poolId + * @param poolId the id of the pool to get tags for * @return comma separated list of tags */ String getStoragePoolTags(long poolId); /** * Returns a list of Strings with tags for the specified storage pool - * @param poolId + * @param poolId the id of the pool to get tags for * @return comma separated list of tags */ List getStoragePoolTagList(long poolId); @@ -273,7 +292,7 @@ static Boolean getFullCloneConfiguration(Long storeId) { Pair sendToPool(StoragePool pool, long[] hostIdsToTryFirst, List hostIdsToAvoid, Command cmd) throws StorageUnavailableException; - public Answer getVolumeStats(StoragePool pool, Command cmd); + Answer getVolumeStats(StoragePool pool, Command cmd); boolean canPoolProvideStorageStats(StoragePool pool); @@ -321,6 +340,8 @@ static Boolean getFullCloneConfiguration(Long storeId) { boolean canHostPrepareStoragePoolAccess(Host host, StoragePool pool); + boolean canDisconnectHostFromStoragePool(Host host, StoragePool pool); + Host getHost(long hostId); Host updateSecondaryStorage(long secStorageId, String newUrl); @@ -415,4 +436,11 @@ void connectHostsToPool(DataStore primaryStore, List hostIds, Scope scope, void validateChildDatastoresToBeAddedInUpState(StoragePoolVO datastoreClusterPool, List childDatastoreAnswerList); + boolean checkIfHostAndStoragePoolHasCommonStorageAccessGroups(Host host, StoragePool pool); + + Pair checkIfReadyVolumeFitsInStoragePoolWithStorageAccessGroups(StoragePool destPool, Volume volume); + + String[] getStorageAccessGroups(Long zoneId, Long podId, Long clusterId, Long hostId); + + CapacityVO getObjectStorageUsedStats(Long zoneId); } diff --git a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java index 1dde19d7a5d1..8c11fe6c93ad 100644 --- a/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java +++ b/engine/components-api/src/main/java/com/cloud/template/TemplateManager.java @@ -29,6 +29,7 @@ import com.cloud.deploy.DeployDestination; import com.cloud.exception.ResourceAllocationException; import com.cloud.exception.StorageUnavailableException; +import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.DataStoreRole; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.StoragePool; @@ -44,6 +45,8 @@ public interface TemplateManager { static final String AllowPublicUserTemplatesCK = "allow.public.user.templates"; static final String TemplatePreloaderPoolSizeCK = "template.preloader.pool.size"; + static final String PublicTemplateSecStorageCopyCK = "secstorage.public.template.copy.max"; + static final String PrivateTemplateSecStorageCopyCK = "secstorage.private.template.copy.max"; static final ConfigKey AllowPublicUserTemplates = new ConfigKey("Advanced", Boolean.class, AllowPublicUserTemplatesCK, "true", "If false, users will not be able to create public Templates.", true, ConfigKey.Scope.Account); @@ -63,6 +66,33 @@ public interface TemplateManager { true, ConfigKey.Scope.Global); + ConfigKey PublicTemplateSecStorageCopy = new ConfigKey("Advanced", Integer.class, + PublicTemplateSecStorageCopyCK, "0", + "Maximum number of secondary storage pools to which a public template is copied. " + + "0 means copy to all secondary storage pools (default behavior).", + true, ConfigKey.Scope.Zone); + + ConfigKey PrivateTemplateSecStorageCopy = new ConfigKey("Advanced", Integer.class, + PrivateTemplateSecStorageCopyCK, "1", + "Maximum number of secondary storage pools to which a private template is copied. " + + "Default is 1 to preserve existing behavior.", + true, ConfigKey.Scope.Zone); + + ConfigKey VmIsoMaxCount = new ConfigKey("Advanced", + Integer.class, + "vm.iso.max.count", "1", + "Maximum number of ISOs that may be attached to a VM.", + true, + ConfigKey.Scope.Cluster); + + // KVM/libvirt maps deviceSeq=3 to hdc (hda/hdb are taken by the root volume on i440fx/IDE). + // user_vm.iso_id has always pointed at this slot; additional cdroms live in vm_iso_map. + int CDROM_PRIMARY_DEVICE_SEQ = 3; + + // Fallback per-VM cdrom cap when the placement host hasn't advertised host.cdrom.max.count + // (older agent, never-deployed VM, etc.). + int DEFAULT_CDROM_MAX_PER_VM = 1; + static final String VMWARE_TOOLS_ISO = "vmware-tools.iso"; static final String XS_TOOLS_ISO = "xs-tools.iso"; @@ -137,6 +167,12 @@ public interface TemplateManager { List getImageStoreByTemplate(long templateId, Long zoneId); + /** + * Max number of secondary storage copies for the template in this zone; {@code 0} means no limit. + * SYSTEM/ROUTING/BUILTIN templates are always exempt (returns {@code 0}). + */ + int getSecStorageCopyLimit(VMTemplateVO template, long zoneId); + TemplateInfo prepareIso(long isoId, long dcId, Long hostId, Long poolId); @@ -150,7 +186,7 @@ public interface TemplateManager { public static final String MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT = "Message.RegisterPublicTemplate.Event"; public static final String MESSAGE_RESET_TEMPLATE_PERMISSION_EVENT = "Message.ResetTemplatePermission.Event"; - TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones); + TemplateType validateTemplateType(BaseCmd cmd, boolean isAdmin, boolean isCrossZones, Hypervisor.HypervisorType hypervisorType); DataStore verifyHeuristicRulesForZone(VMTemplateVO template, Long zoneId); diff --git a/engine/components-api/src/main/java/com/cloud/vm/VirtualMachineProfileImpl.java b/engine/components-api/src/main/java/com/cloud/vm/VirtualMachineProfileImpl.java index a1c54b90328b..29b96dcc917a 100644 --- a/engine/components-api/src/main/java/com/cloud/vm/VirtualMachineProfileImpl.java +++ b/engine/components-api/src/main/java/com/cloud/vm/VirtualMachineProfileImpl.java @@ -40,7 +40,7 @@ public class VirtualMachineProfileImpl implements VirtualMachineProfile { VirtualMachine _vm; ServiceOffering _offering; VirtualMachineTemplate _template; - UserVmDetailVO _userVmDetails; + VMInstanceDetailVO _userVmDetails; Map _params; List _nics = new ArrayList(); List _disks = new ArrayList(); diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkDeleteBackup.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkDeleteBackup.java new file mode 100644 index 000000000000..b9d2907ef780 --- /dev/null +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkDeleteBackup.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +public class VmWorkDeleteBackup extends VmWork { + + private long backupId; + + private boolean forced; + + public VmWorkDeleteBackup(long userId, long accountId, long vmId, String handlerName, long backupId, boolean forced) { + super(userId, accountId, vmId, handlerName); + this.backupId = backupId; + this.forced = forced; + } + + public long getBackupId() { + return backupId; + } + + public boolean isForced() { + return forced; + } +} diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkRestoreBackup.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkRestoreBackup.java new file mode 100644 index 000000000000..421430cfbe9d --- /dev/null +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkRestoreBackup.java @@ -0,0 +1,45 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +public class VmWorkRestoreBackup extends VmWork { + + private long backupId; + + private boolean quickRestore; + + private Long hostId; + + public VmWorkRestoreBackup(long userId, long accountId, long vmId, String handlerName, long backupId, boolean quickRestore, Long hostId) { + super(userId, accountId, vmId, handlerName); + this.backupId = backupId; + this.quickRestore = quickRestore; + this.hostId = hostId; + } + + public long getBackupId() { + return backupId; + } + + public boolean isQuickRestore() { + return quickRestore; + } + + public Long getHostId() { + return hostId; + } +} diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkRestoreVolumeBackupAndAttach.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkRestoreVolumeBackupAndAttach.java new file mode 100644 index 000000000000..a34b11abbdb4 --- /dev/null +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkRestoreVolumeBackupAndAttach.java @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import org.apache.cloudstack.backup.Backup; + +public class VmWorkRestoreVolumeBackupAndAttach extends VmWork { + + private long backupId; + + private Backup.VolumeInfo backupVolumeInfo; + + private String hostIp; + + private boolean quickRestore; + + public VmWorkRestoreVolumeBackupAndAttach(long userId, long accountId, long vmId, String handlerName, long backupId, Backup.VolumeInfo backupVolumeInfo, + String hostIp, boolean quickRestore) { + super(userId, accountId, vmId, handlerName); + this.backupId = backupId; + this.backupVolumeInfo = backupVolumeInfo; + this.hostIp = hostIp; + this.quickRestore = quickRestore; + } + + public long getBackupId() { + return backupId; + } + + public Backup.VolumeInfo getBackupVolumeInfo() { + return backupVolumeInfo; + } + + public String getHostIp() { + return hostIp; + } + + public boolean isQuickRestore() { + return quickRestore; + } +} diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkSerializer.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkSerializer.java index bd6b52d261fa..e4fdc0c4f375 100644 --- a/engine/components-api/src/main/java/com/cloud/vm/VmWorkSerializer.java +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkSerializer.java @@ -61,7 +61,6 @@ public static String serialize(VmWork work) { // use java binary serialization instead // return JobSerializerHelper.toObjectSerializedString(work); - // return s_gson.toJson(work); } public static T deserialize(Class clazz, String workInJsonText) { @@ -69,6 +68,5 @@ public static T deserialize(Class clazz, String workInJson // use java binary serialization instead // return (T)JobSerializerHelper.fromObjectSerializedString(workInJsonText); - // return (T)s_gson.fromJson(workInJsonText, clazz); } } diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeBackup.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeBackup.java new file mode 100644 index 000000000000..57367d368b86 --- /dev/null +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeBackup.java @@ -0,0 +1,50 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +public class VmWorkTakeBackup extends VmWork { + + private long backupId; + + private boolean quiesceVm; + + private boolean isolated; + + public VmWorkTakeBackup(long userId, long accountId, long vmId, long backupId, String handlerName, boolean quiesceVm, boolean isolated) { + super(userId, accountId, vmId, handlerName); + this.quiesceVm = quiesceVm; + this.backupId = backupId; + this.isolated = isolated; + } + + public boolean isQuiesceVm() { + return quiesceVm; + } + + public long getBackupId() { + return backupId; + } + + public boolean isIsolated() { + return isolated; + } + + @Override + public String toString() { + return super.toStringAfterRemoveParams(null, null); + } +} diff --git a/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java b/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java index 8474052be201..88d25441e0ac 100644 --- a/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java +++ b/engine/components-api/src/main/java/com/cloud/vm/VmWorkTakeVolumeSnapshot.java @@ -30,12 +30,12 @@ public class VmWorkTakeVolumeSnapshot extends VmWork { private boolean quiesceVm; private Snapshot.LocationType locationType; private boolean asyncBackup; - + private List poolIds; private List zoneIds; public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String handlerName, Long volumeId, Long policyId, Long snapshotId, boolean quiesceVm, Snapshot.LocationType locationType, - boolean asyncBackup, List zoneIds) { + boolean asyncBackup, List zoneIds, List poolIds) { super(userId, accountId, vmId, handlerName); this.volumeId = volumeId; this.policyId = policyId; @@ -44,6 +44,7 @@ public VmWorkTakeVolumeSnapshot(long userId, long accountId, long vmId, String h this.locationType = locationType; this.asyncBackup = asyncBackup; this.zoneIds = zoneIds; + this.poolIds = poolIds; } public Long getVolumeId() { @@ -71,4 +72,8 @@ public boolean isAsyncBackup() { public List getZoneIds() { return zoneIds; } + + public List getPoolIds() { + return poolIds; + } } diff --git a/engine/components-api/src/main/java/com/cloud/vm/snapshot/VMSnapshotManager.java b/engine/components-api/src/main/java/com/cloud/vm/snapshot/VMSnapshotManager.java index 997b413c099c..6831552b83db 100644 --- a/engine/components-api/src/main/java/com/cloud/vm/snapshot/VMSnapshotManager.java +++ b/engine/components-api/src/main/java/com/cloud/vm/snapshot/VMSnapshotManager.java @@ -31,7 +31,7 @@ public interface VMSnapshotManager extends VMSnapshotService, Manager { static final ConfigKey VMSnapshotExpireInterval = new ConfigKey("Advanced", Integer.class, "vmsnapshot.expire.interval", "-1", "VM Snapshot expire interval in hours", true, ConfigKey.Scope.Account); - ConfigKey VMSnapshotMax = new ConfigKey("Advanced", Integer.class, "vmsnapshot.max", "10", "Maximum vm snapshots for a single vm", true, ConfigKey.Scope.Global); + ConfigKey VMSnapshotMax = new ConfigKey("Advanced", Integer.class, "vmsnapshot.max", "10", "Maximum VM snapshots for a single VM", true, ConfigKey.Scope.Account); /** * Delete all VM snapshots belonging to one VM @@ -42,7 +42,7 @@ public interface VMSnapshotManager extends VMSnapshotService, Manager { boolean deleteAllVMSnapshots(long id, VMSnapshot.Type type); /** - * Sync VM snapshot state when VM snapshot in reverting or snapshoting or expunging state + * Sync VM snapshot state when VM snapshot in reverting or snapshotting or expunging state * Used for fullsync after agent connects * * @param vm, the VM in question diff --git a/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java b/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java index feb7ee46aec5..f80ba9580d5d 100644 --- a/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java +++ b/engine/components-api/src/test/java/com/cloud/vm/VmWorkTakeVolumeSnapshotTest.java @@ -26,8 +26,9 @@ public class VmWorkTakeVolumeSnapshotTest { @Test public void testVmWorkTakeVolumeSnapshotZoneIds() { List zoneIds = List.of(10L, 20L); + List poolIds = List.of(10L, 20L); VmWorkTakeVolumeSnapshot work = new VmWorkTakeVolumeSnapshot(1L, 1L, 1L, "handler", - 1L, 1L, 1L, false, null, false, zoneIds); + 1L, 1L, 1L, false, null, false, zoneIds, poolIds); Assert.assertNotNull(work.getZoneIds()); Assert.assertEquals(zoneIds.size(), work.getZoneIds().size()); Assert.assertEquals(zoneIds.get(0), work.getZoneIds().get(0)); diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml index 9f20012b56ec..fda63d2558b0 100755 --- a/engine/orchestration/pom.xml +++ b/engine/orchestration/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml @@ -70,7 +70,12 @@ org.apache.cloudstack - cloud-plugin-shutdown + cloud-plugin-maintenance + ${project.version} + + + org.apache.cloudstack + cloud-plugin-hypervisor-external ${project.version} diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java index 30a58d405c9e..0a6bbc876544 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentAttache.java @@ -32,7 +32,11 @@ import java.util.concurrent.TimeUnit; import com.cloud.agent.api.CleanupPersistentNetworkResourceCommand; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.utils.Pair; +import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.agent.lb.SetupMSListCommand; +import org.apache.cloudstack.command.ReconcileAnswer; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.logging.log4j.Logger; @@ -114,6 +118,7 @@ public int compare(final Object o1, final Object o2) { protected final long _id; protected String _uuid; protected String _name = null; + protected HypervisorType _hypervisorType; protected final ConcurrentHashMap _waitForList; protected final LinkedList _requests; protected Long _currentSequence; @@ -135,10 +140,11 @@ public int compare(final Object o1, final Object o2) { Arrays.sort(s_commandsNotAllowedInConnectingMode); } - protected AgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final boolean maintenance) { + protected AgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final HypervisorType hypervisorType, final boolean maintenance) { _id = id; _uuid = uuid; _name = name; + _hypervisorType = hypervisorType; _waitForList = new ConcurrentHashMap(); _currentSequence = null; _maintenance = maintenance; @@ -261,6 +267,10 @@ public String getName() { return _name; } + public HypervisorType get_hypervisorType() { + return _hypervisorType; + } + public int getQueueSize() { return _requests.size(); } @@ -406,10 +416,17 @@ public Answer[] send(final Request req, final int wait) throws AgentUnavailableE try { for (int i = 0; i < 2; i++) { Answer[] answers = null; - try { - answers = sl.waitFor(wait); - } catch (final InterruptedException e) { - logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted"); + Command[] cmds = req.getCommands(); + if (cmds != null && cmds.length == 1 && (cmds[0] != null) && cmds[0].isReconcile() + && !sl.isDisconnected() && _agentMgr.isReconcileCommandsEnabled(_hypervisorType)) { + // only available if (1) the only command is a Reconcile command (2) agent is connected; (3) reconciliation is enabled; (4) hypervisor is KVM; + answers = waitForAnswerOfReconcileCommand(sl, seq, cmds[0], wait); + } else { + try { + answers = sl.waitFor(wait); + } catch (final InterruptedException e) { + logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted"); + } } if (answers != null) { new Response(req, answers).logD("Received: ", false); @@ -428,11 +445,13 @@ public Answer[] send(final Request req, final int wait) throws AgentUnavailableE if (current != null && seq != current) { logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Waited too long."); + _agentMgr.updateReconcileCommandsIfNeeded(req.getSequence(), req.getCommands(), Command.State.TIMED_OUT); throw new OperationTimedoutException(req.getCommands(), _id, seq, wait, false); } logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Waiting some more time because this is the current command"); } + _agentMgr.updateReconcileCommandsIfNeeded(req.getSequence(), req.getCommands(), Command.State.TIMED_OUT); throw new OperationTimedoutException(req.getCommands(), _id, seq, wait * 2, true); } catch (OperationTimedoutException e) { logger.warn(LOG_SEQ_FORMATTED_STRING, seq, "Timed out on " + req.toString()); @@ -449,12 +468,57 @@ public Answer[] send(final Request req, final int wait) throws AgentUnavailableE if (req.executeInSequence() && (current != null && current == seq)) { sendNext(seq); } + _agentMgr.updateReconcileCommandsIfNeeded(req.getSequence(), req.getCommands(), Command.State.TIMED_OUT); throw new OperationTimedoutException(req.getCommands(), _id, seq, wait, false); } finally { unregisterListener(seq); } } + private Answer[] waitForAnswerOfReconcileCommand(SynchronousListener sl, final long seq, final Command command, final int wait) { + Answer[] answers = null; + int waitTimeLeft = wait; + while (waitTimeLeft > 0) { + int waitTime = Math.min(waitTimeLeft, _agentMgr.getReconcileInterval()); + logger.debug(String.format("Waiting %s seconds for the answer of reconcile command %s-%s", waitTime, seq, command)); + if (sl.isDisconnected()) { + logger.debug(String.format("Disconnected while waiting for the answer of reconcile command %s-%s", seq, command)); + break; + } + try { + answers = sl.waitFor(waitTime); + } catch (final InterruptedException e) { + logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted"); + } + if (answers != null) { + break; + } + + logger.debug(String.format("Getting the answer of reconcile command from cloudstack database for %s-%s", seq, command)); + Pair commandInfo = _agentMgr.getStateAndAnswerOfReconcileCommand(seq, command); + if (commandInfo == null) { + logger.debug(String.format("Cannot get the answer of reconcile command from cloudstack database for %s-%s", seq, command)); + continue; + } + Command.State state = commandInfo.first(); + if (Command.State.INTERRUPTED.equals(state)) { + logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Interrupted by agent, will reconcile it"); + throw new CloudRuntimeException("Interrupted by agent"); + } else if (Command.State.DANGLED_IN_BACKEND.equals(state)) { + logger.debug(LOG_SEQ_FORMATTED_STRING, seq, "Dangling in backend, it seems the agent was restarted, will reconcile it"); + throw new CloudRuntimeException("It is not being processed by agent"); + } + Answer answer = commandInfo.second(); + logger.debug(String.format("Got the answer of reconcile command from cloudstack database for %s-%s: %s", seq, command, answer)); + if (answer != null && !(answer instanceof ReconcileAnswer)) { + answers = new Answer[] { answer }; + break; + } + waitTimeLeft -= waitTime; + } + return answers; + } + protected synchronized void sendNext(final long seq) { _currentSequence = null; if (_requests.isEmpty()) { diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java index ebe0465e3f0f..1215829d92f8 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.agent.manager; +import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.net.SocketAddress; @@ -26,6 +27,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; @@ -43,13 +45,20 @@ import com.cloud.utils.StringUtils; import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.ca.CAManager; +import org.apache.cloudstack.command.ReconcileCommandService; +import org.apache.cloudstack.command.ReconcileCommandUtils; +import org.apache.cloudstack.command.ReconcileCommandVO; +import org.apache.cloudstack.command.dao.ReconcileCommandDao; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; 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.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; +import org.apache.cloudstack.maintenance.ManagementServerMaintenanceListener; +import org.apache.cloudstack.maintenance.ManagementServerMaintenanceManager; import org.apache.cloudstack.managed.context.ManagedContextRunnable; +import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -83,6 +92,8 @@ import com.cloud.agent.transport.Request; import com.cloud.agent.transport.Response; import com.cloud.alert.AlertManager; +import com.cloud.cluster.ManagementServerHostVO; +import com.cloud.cluster.dao.ManagementServerHostDao; import com.cloud.configuration.Config; import com.cloud.configuration.ManagementServiceConfiguration; import com.cloud.dc.ClusterVO; @@ -133,17 +144,17 @@ /** * Implementation of the Agent Manager. This class controls the connection to the agents. **/ -public class AgentManagerImpl extends ManagerBase implements AgentManager, HandlerFactory, Configurable { +public class AgentManagerImpl extends ManagerBase implements AgentManager, HandlerFactory, ManagementServerMaintenanceListener, Configurable { /** * _agents is a ConcurrentHashMap, but it is used from within a synchronized block. This will be reported by findbugs as JLM_JSR166_UTILCONCURRENT_MONITORENTER. Maybe a * ConcurrentHashMap is not the right thing to use here, but i'm not sure so i leave it alone. */ - protected ConcurrentHashMap _agents = new ConcurrentHashMap(10007); - protected List> _hostMonitors = new ArrayList>(17); - protected List> _cmdMonitors = new ArrayList>(17); - protected List> _creationMonitors = new ArrayList>(17); - protected List _loadingAgents = new ArrayList(); + protected ConcurrentHashMap _agents = new ConcurrentHashMap<>(10007); + protected List> _hostMonitors = new ArrayList<>(17); + protected List> _cmdMonitors = new ArrayList<>(17); + protected List> _creationMonitors = new ArrayList<>(17); + protected List _loadingAgents = new ArrayList<>(); protected Map _commandTimeouts = new HashMap<>(); private int _monitorId = 0; @@ -156,6 +167,8 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl @Inject protected HostDao _hostDao = null; @Inject + private ManagementServerHostDao _mshostDao; + @Inject protected OutOfBandManagementDao outOfBandManagementDao; @Inject protected DataCenterDao _dcDao = null; @@ -170,6 +183,10 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl protected HighAvailabilityManager _haMgr = null; @Inject protected AlertManager _alertMgr = null; + @Inject + protected ReconcileCommandService reconcileCommandService; + @Inject + ReconcileCommandDao reconcileCommandDao; @Inject protected HypervisorGuruManager _hvGuruMgr; @@ -177,6 +194,9 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl @Inject protected IndirectAgentLB indirectAgentLB; + @Inject + private ManagementServerMaintenanceManager managementServerMaintenanceManager; + protected int _retry = 2; protected long _nodeId = -1; @@ -189,39 +209,44 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl private int _directAgentThreadCap; + private List lastAgents = null; + protected StateMachine2 _statusStateMachine = Status.getStateMachine(); - private final ConcurrentHashMap _pingMap = new ConcurrentHashMap(10007); + private final ConcurrentHashMap _pingMap = new ConcurrentHashMap<>(10007); private int maxConcurrentNewAgentConnections; private final ConcurrentHashMap newAgentConnections = new ConcurrentHashMap<>(); protected ScheduledExecutorService newAgentConnectionsMonitor; + private boolean _reconcileCommandsEnabled = false; + private Integer _reconcileCommandInterval; + @Inject ResourceManager _resourceMgr; @Inject ManagementServiceConfiguration mgmtServiceConf; - protected final ConfigKey Workers = new ConfigKey("Advanced", Integer.class, "workers", "5", + protected final ConfigKey Workers = new ConfigKey<>("Advanced", Integer.class, "workers", "5", "Number of worker threads handling remote agent connections.", false); - protected final ConfigKey Port = new ConfigKey("Advanced", Integer.class, "port", "8250", "Port to listen on for remote agent connections.", false); + protected final ConfigKey Port = new ConfigKey<>("Advanced", Integer.class, "port", "8250", "Port to listen on for remote (indirect) agent connections.", false); protected final ConfigKey RemoteAgentSslHandshakeTimeout = new ConfigKey<>("Advanced", Integer.class, "agent.ssl.handshake.timeout", "30", - "Seconds after which SSL handshake times out during remote agent connections.", false); + "Seconds after which SSL handshake times out during remote (indirect) agent connections.", false); protected final ConfigKey RemoteAgentMaxConcurrentNewConnections = new ConfigKey<>("Advanced", Integer.class, "agent.max.concurrent.new.connections", "0", - "Number of maximum concurrent new connections server allows for remote agents. " + + "Number of maximum concurrent new connections server allows for remote (indirect) agents. " + "If set to zero (default value) then no limit will be enforced on concurrent new connections", false); protected final ConfigKey RemoteAgentNewConnectionsMonitorInterval = new ConfigKey<>("Advanced", Integer.class, "agent.connections.monitor.interval", "1800", "Time in seconds to monitor the new agent connections and cleanup the expired connections.", false); - protected final ConfigKey AlertWait = new ConfigKey("Advanced", Integer.class, "alert.wait", "1800", + protected final ConfigKey AlertWait = new ConfigKey<>("Advanced", Integer.class, "alert.wait", "1800", "Seconds to wait before alerting on a disconnected agent", true); - protected final ConfigKey DirectAgentLoadSize = new ConfigKey("Advanced", Integer.class, "direct.agent.load.size", "16", + protected final ConfigKey DirectAgentLoadSize = new ConfigKey<>("Advanced", Integer.class, "direct.agent.load.size", "16", "The number of direct agents to load each time", false); - protected final ConfigKey DirectAgentPoolSize = new ConfigKey("Advanced", Integer.class, "direct.agent.pool.size", "500", + protected final ConfigKey DirectAgentPoolSize = new ConfigKey<>("Advanced", Integer.class, "direct.agent.pool.size", "500", "Default size for DirectAgentPool", false); - protected final ConfigKey DirectAgentThreadCap = new ConfigKey("Advanced", Float.class, "direct.agent.thread.cap", "1", + protected final ConfigKey DirectAgentThreadCap = new ConfigKey<>("Advanced", Float.class, "direct.agent.thread.cap", "1", "Percentage (as a value between 0 and 1) of direct.agent.pool.size to be used as upper thread cap for a single direct agent to process requests", false); - protected final ConfigKey CheckTxnBeforeSending = new ConfigKey("Developer", Boolean.class, "check.txn.before.sending.agent.commands", "false", + protected final ConfigKey CheckTxnBeforeSending = new ConfigKey<>("Developer", Boolean.class, "check.txn.before.sending.agent.commands", "false", "This parameter allows developers to enable a check to see if a transaction wraps commands that are sent to the resource. This is not to be enabled on production systems.", true); public static final List HOST_DOWN_ALERT_UNSUPPORTED_HOST_TYPES = Arrays.asList( @@ -244,12 +269,11 @@ public boolean configure(final String name, final Map params) th registerForHostEvents(new SetHostParamsListener(), true, true, false); + managementServerMaintenanceManager.registerListener(this); + final int agentTaskThreads = DirectAgentLoadSize.value(); - _executor = new ThreadPoolExecutor(agentTaskThreads, agentTaskThreads, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("AgentTaskPool")); - _connectExecutor = new ThreadPoolExecutor(100, 500, 60l, TimeUnit.SECONDS, new LinkedBlockingQueue(), new NamedThreadFactory("AgentConnectTaskPool")); - // allow core threads to time out even when there are no items in the queue - _connectExecutor.allowCoreThreadTimeOut(true); + _executor = new ThreadPoolExecutor(agentTaskThreads, agentTaskThreads, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory("AgentTaskPool")); maxConcurrentNewAgentConnections = RemoteAgentMaxConcurrentNewConnections.value(); @@ -265,12 +289,11 @@ public boolean configure(final String name, final Map params) th logger.debug("Created DirectAgentAttache pool with size: {}.", directAgentPoolSize); _directAgentThreadCap = Math.round(directAgentPoolSize * DirectAgentThreadCap.value()) + 1; // add 1 to always make the value > 0 - _monitorExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("AgentMonitor")); - - newAgentConnectionsMonitor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("NewAgentConnectionsMonitor")); - initializeCommandTimeouts(); + _reconcileCommandsEnabled = ReconcileCommandService.ReconcileCommandsEnabled.value(); + _reconcileCommandInterval = ReconcileCommandService.ReconcileCommandsInterval.value(); + return true; } @@ -291,13 +314,13 @@ public int getNewConnectionsCount() { @Override public void registerNewConnection(SocketAddress address) { - logger.trace(String.format("Adding new agent connection from %s", address.toString())); + logger.trace("Adding new agent connection from {}", address.toString()); newAgentConnections.putIfAbsent(address.toString(), System.currentTimeMillis()); } @Override public void unregisterNewConnection(SocketAddress address) { - logger.trace(String.format("Removing new agent connection for %s", address.toString())); + logger.trace("Removing new agent connection for {}", address.toString()); newAgentConnections.remove(address.toString()); } @@ -307,16 +330,16 @@ public int registerForHostEvents(final Listener listener, final boolean connecti _monitorId++; if (connections) { if (priority) { - _hostMonitors.add(0, new Pair(_monitorId, listener)); + _hostMonitors.add(0, new Pair<>(_monitorId, listener)); } else { - _hostMonitors.add(new Pair(_monitorId, listener)); + _hostMonitors.add(new Pair<>(_monitorId, listener)); } } if (commands) { if (priority) { - _cmdMonitors.add(0, new Pair(_monitorId, listener)); + _cmdMonitors.add(0, new Pair<>(_monitorId, listener)); } else { - _cmdMonitors.add(new Pair(_monitorId, listener)); + _cmdMonitors.add(new Pair<>(_monitorId, listener)); } } logger.debug("Registering listener {} with id {}", listener.getClass().getSimpleName(), _monitorId); @@ -329,9 +352,9 @@ public int registerForInitialConnects(final StartupCommandProcessor creator, fin synchronized (_hostMonitors) { _monitorId++; if (priority) { - _creationMonitors.add(0, new Pair(_monitorId, creator)); + _creationMonitors.add(0, new Pair<>(_monitorId, creator)); } else { - _creationMonitors.add(new Pair(_monitorId, creator)); + _creationMonitors.add(new Pair<>(_monitorId, creator)); } return _monitorId; } @@ -343,8 +366,81 @@ public void unregisterForHostEvents(final int id) { _hostMonitors.remove(id); } + @Override + public void onManagementServerPreparingForMaintenance() { + logger.debug("Management server preparing for maintenance"); + if (_connection != null) { + _connection.block(); + } + } + + @Override + public void onManagementServerCancelPreparingForMaintenance() { + logger.debug("Management server cancel preparing for maintenance"); + if (_connection != null) { + _connection.unblock(); + } + } + + @Override + public void onManagementServerMaintenance() { + logger.debug("Management server maintenance enabled"); + _monitorExecutor.shutdownNow(); + newAgentConnectionsMonitor.shutdownNow(); + if (_connection != null) { + _connection.stop(); + + try { + _connection.cleanUp(); + } catch (final IOException e) { + logger.warn("Fail to clean up old connection", e); + } + } + _connectExecutor.shutdownNow(); + } + + @Override + public void onManagementServerCancelMaintenance() { + logger.debug("Management server maintenance disabled"); + if (_connectExecutor.isShutdown()) { + initConnectExecutor(); + } + startDirectlyConnectedHosts(true); + if (_connection != null) { + try { + _connection.start(); + } catch (final NioConnectionException e) { + logger.error("Error when connecting to the NioServer!", e); + } + } + + if (_monitorExecutor.isShutdown()) { + initAndScheduleMonitorExecutor(); + } + if (newAgentConnectionsMonitor.isShutdown()) { + initAndScheduleAgentConnectionsMonitor(); + } + } + + private void initConnectExecutor() { + _connectExecutor = new ThreadPoolExecutor(100, 500, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(), new NamedThreadFactory("AgentConnectTaskPool")); + // allow core threads to time out even when there are no items in the queue + _connectExecutor.allowCoreThreadTimeOut(true); + } + + private void initAndScheduleMonitorExecutor() { + _monitorExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("AgentMonitor")); + _monitorExecutor.scheduleWithFixedDelay(new MonitorTask(), mgmtServiceConf.getPingInterval(), mgmtServiceConf.getPingInterval(), TimeUnit.SECONDS); + } + + private void initAndScheduleAgentConnectionsMonitor() { + final int cleanupTimeInSecs = Wait.value(); + newAgentConnectionsMonitor = Executors.newScheduledThreadPool(1, new NamedThreadFactory("NewAgentConnectionsMonitor")); + newAgentConnectionsMonitor.scheduleAtFixedRate(new AgentNewConnectionsMonitorTask(), cleanupTimeInSecs, cleanupTimeInSecs, TimeUnit.SECONDS); + } + private AgentControlAnswer handleControlCommand(final AgentAttache attache, final AgentControlCommand cmd) { - AgentControlAnswer answer = null; + AgentControlAnswer answer; for (final Pair listener : _cmdMonitors) { answer = listener.second().processControlCommand(attache.getId(), cmd); @@ -372,7 +468,7 @@ public void notifyAnswersToMonitors(final long agentId, final long seq, final An } public AgentAttache findAttache(final long hostId) { - AgentAttache attache = null; + AgentAttache attache; synchronized (_agents) { attache = _agents.get(hostId); } @@ -414,12 +510,10 @@ public Answer send(final Long hostId, final Command cmd) throws AgentUnavailable cmds.addCommand(cmd); send(hostId, cmds, cmd.getWait()); final Answer[] answers = cmds.getAnswers(); - if (answers != null && !(answers[0] instanceof UnsupportedAnswer)) { - return answers[0]; - } - - if (answers != null && answers[0] instanceof UnsupportedAnswer) { - logger.warn("Unsupported Command: {}", answers[0].getDetails()); + if (answers != null) { + if (answers[0] instanceof UnsupportedAnswer) { + logger.warn("Unsupported Command: {}", answers[0].getDetails()); + } return answers[0]; } @@ -450,8 +544,8 @@ private static void tagCommand(final Command cmd) { } /** - * @param commands - * @return + * @param commands object container of commands + * @return array of commands */ private Command[] checkForCommandsAndTag(final Commands commands) { final Command[] cmds = commands.toCommands(); @@ -467,8 +561,8 @@ private Command[] checkForCommandsAndTag(final Commands commands) { } /** - * @param commands - * @param cmds + * @param commands object container of commands + * @param cmds array of commands */ private void setEmptyAnswers(final Commands commands, final Command[] cmds) { if (cmds.length == 0) { @@ -507,7 +601,7 @@ private void initializeCommandTimeouts() { String commandWaits = GranularWaitTimeForCommands.value().trim(); if (StringUtils.isNotEmpty(commandWaits)) { _commandTimeouts = getCommandTimeoutsMap(commandWaits); - logger.info(String.format("Timeouts for management server internal commands successfully initialized from global setting commands.timeout: %s", _commandTimeouts)); + logger.info("Timeouts for management server internal commands successfully initialized from global setting commands.timeout: {}", _commandTimeouts); } } @@ -523,10 +617,10 @@ private Map getCommandTimeoutsMap(String commandWaits) { int commandTimeout = Integer.parseInt(parts[1].trim()); commandTimeouts.put(commandName, commandTimeout); } catch (NumberFormatException e) { - logger.error(String.format("Initialising the timeouts using commands.timeout: %s for management server internal commands failed with error %s", commandPair, e.getMessage())); + logger.error("Initialising the timeouts using commands.timeout: {} for management server internal commands failed with error {}", commandPair, e.getMessage()); } } else { - logger.error(String.format("Error initialising the timeouts for management server internal commands. Invalid format in commands.timeout: %s", commandPair)); + logger.error("Error initialising the timeouts for management server internal commands. Invalid format in commands.timeout: {}", commandPair); } } return commandTimeouts; @@ -540,7 +634,7 @@ public Answer[] send(final Long hostId, final Commands commands, int timeout) th } int wait = getTimeout(commands, timeout); - logger.debug(String.format("Wait time setting on %s is %d seconds", commands, wait)); + logger.debug("Wait time setting on {} is {} seconds", commands, wait); for (Command cmd : commands) { String simpleCommandName = cmd.getClass().getSimpleName(); Integer commandTimeout = _commandTimeouts.get(simpleCommandName); @@ -568,7 +662,13 @@ public Answer[] send(final Long hostId, final Commands commands, int timeout) th final Request req = new Request(hostId, agent.getName(), _nodeId, cmds, commands.stopOnError(), true); req.setSequence(agent.getNextSequence()); + + reconcileCommandService.persistReconcileCommands(hostId, req.getSequence(), cmds); + final Answer[] answers = agent.send(req, wait); + + reconcileCommandService.processAnswers(req.getSequence(), cmds, answers); + notifyAnswersToMonitors(hostId, req.getSequence(), answers); commands.setAnswers(answers); return answers; @@ -577,17 +677,17 @@ public Answer[] send(final Long hostId, final Commands commands, int timeout) th protected Status investigate(final AgentAttache agent) { final Long hostId = agent.getId(); final HostVO host = _hostDao.findById(hostId); - if (host != null && host.getType() != null && !host.getType().isVirtual()) { - logger.debug("Checking if agent ({}) is alive", host); - final Answer answer = easySend(hostId, new CheckHealthCommand()); - if (answer != null && answer.getResult()) { - final Status status = Status.Up; - logger.debug("Agent ({}) responded to checkHealthCommand, reporting that agent is {}", host, status); - return status; - } - return _haMgr.investigate(hostId); + if (host == null || host.getType() == null || host.getType().isVirtual()) { + return Status.Alert; + } + logger.debug("Checking if agent ({}) is alive", host); + final Answer answer = easySend(hostId, new CheckHealthCommand()); + if (answer != null && answer.getResult()) { + final Status status = Status.Up; + logger.debug("Agent ({}) responded to checkHealthCommand, reporting that agent is {}", host, status); + return status; } - return Status.Alert; + return _haMgr.investigate(hostId); } protected AgentAttache getAttache(final Long hostId) throws AgentUnavailableException { @@ -627,7 +727,7 @@ public void removeAgent(final AgentAttache attache, final Status nextState) { } final long hostId = attache.getId(); logger.debug("Remove Agent : {}", attache); - AgentAttache removed = null; + AgentAttache removed; boolean conflict = false; synchronized (_agents) { removed = _agents.remove(hostId); @@ -659,15 +759,15 @@ public void notifyMonitorsOfNewlyAddedHost(long hostId) { } } - protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, final StartupCommand[] cmd, final boolean forRebalance) throws ConnectionException { + protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, final StartupCommand[] cmds, final boolean forRebalance) throws ConnectionException { final long hostId = attache.getId(); final HostVO host = _hostDao.findById(hostId); for (final Pair monitor : _hostMonitors) { - logger.debug("Sending Connect to listener: {}", monitor.second().getClass().getSimpleName()); - for (int i = 0; i < cmd.length; i++) { + logger.debug("Sending Connect to listener: {}, for rebalance: {}", monitor.second().getClass().getSimpleName(), forRebalance); + for (StartupCommand cmd : cmds) { try { - logger.debug("process connection to issue {} forRebalance == {}", ReflectionToStringBuilderUtils.reflectCollection(cmd[i]), forRebalance); - monitor.second().processConnect(host, cmd[i], forRebalance); + logger.debug("process connection to issue: {} for host: {}, forRebalance: {}", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(cmd, "id", "type", "msHostList", "connectionTransferred"), hostId, forRebalance); + monitor.second().processConnect(host, cmd, forRebalance); } catch (final ConnectionException ce) { if (ce.isSetupError()) { logger.warn("Monitor {} says there is an error in the connect process for {} due to {}", monitor.second().getClass().getSimpleName(), hostId, ce.getMessage()); @@ -680,16 +780,15 @@ protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, fi } } catch (final HypervisorVersionChangedException hvce) { handleDisconnectWithoutInvestigation(attache, Event.ShutdownRequested, true, true); - throw new CloudRuntimeException("Unable to connect " + (attache == null ? "" : attache.getId()), hvce); + throw new CloudRuntimeException("Unable to connect " + attache.getId(), hvce); } catch (final Exception e) { logger.error("Monitor {} says there is an error in the connect process for {} due to {}", monitor.second().getClass().getSimpleName(), hostId, e.getMessage(), e); handleDisconnectWithoutInvestigation(attache, Event.AgentDisconnected, true, true); - throw new CloudRuntimeException("Unable to connect " + (attache == null ? "" : attache.getId()), e); + throw new CloudRuntimeException("Unable to connect " + attache.getId(), e); } } } - final Long dcId = host.getDataCenterId(); final ReadyCommand ready = new ReadyCommand(host, NumbersUtil.enableHumanReadableSizes); ready.setWait(ReadyCommandWait.value()); final Answer answer = easySend(hostId, ready); @@ -706,8 +805,11 @@ protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, fi String uefiEnabled = detailsMap.get(Host.HOST_UEFI_ENABLE); String virtv2vVersion = detailsMap.get(Host.HOST_VIRTV2V_VERSION); String ovftoolVersion = detailsMap.get(Host.HOST_OVFTOOL_VERSION); + String vddkSupport = detailsMap.get(Host.HOST_VDDK_SUPPORT); + String vddkLibDir = detailsMap.get(Host.HOST_VDDK_LIB_DIR); + String vddkVersion = detailsMap.get(Host.HOST_VDDK_VERSION); logger.debug("Got HOST_UEFI_ENABLE [{}] for host [{}]:", uefiEnabled, host); - if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion)) { + if (ObjectUtils.anyNotNull(uefiEnabled, virtv2vVersion, ovftoolVersion, vddkSupport, vddkLibDir, vddkVersion)) { _hostDao.loadDetails(host); boolean updateNeeded = false; if (StringUtils.isNotBlank(uefiEnabled) && !uefiEnabled.equals(host.getDetails().get(Host.HOST_UEFI_ENABLE))) { @@ -722,6 +824,26 @@ protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, fi host.getDetails().put(Host.HOST_OVFTOOL_VERSION, ovftoolVersion); updateNeeded = true; } + if (StringUtils.isNotBlank(vddkSupport) && !vddkSupport.equals(host.getDetails().get(Host.HOST_VDDK_SUPPORT))) { + host.getDetails().put(Host.HOST_VDDK_SUPPORT, vddkSupport); + updateNeeded = true; + } + if (!StringUtils.defaultString(vddkLibDir).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_VDDK_LIB_DIR)))) { + if (StringUtils.isBlank(vddkLibDir)) { + host.getDetails().remove(Host.HOST_VDDK_LIB_DIR); + } else { + host.getDetails().put(Host.HOST_VDDK_LIB_DIR, vddkLibDir); + } + updateNeeded = true; + } + if (!StringUtils.defaultString(vddkVersion).equals(StringUtils.defaultString(host.getDetails().get(Host.HOST_VDDK_VERSION)))) { + if (StringUtils.isBlank(vddkVersion)) { + host.getDetails().remove(Host.HOST_VDDK_VERSION); + } else { + host.getDetails().put(Host.HOST_VDDK_VERSION, vddkVersion); + } + updateNeeded = true; + } if (updateNeeded) { _hostDao.saveDetails(host); } @@ -736,7 +858,15 @@ protected AgentAttache notifyMonitorsOfConnection(final AgentAttache attache, fi @Override public boolean start() { - startDirectlyConnectedHosts(); + ManagementServerHostVO msHost = _mshostDao.findByMsid(_nodeId); + if (msHost != null && (ManagementServerHost.State.Maintenance.equals(msHost.getState()) || ManagementServerHost.State.PreparingForMaintenance.equals(msHost.getState()))) { + _monitorExecutor.shutdownNow(); + newAgentConnectionsMonitor.shutdownNow(); + return true; + } + + initConnectExecutor(); + startDirectlyConnectedHosts(false); if (_connection != null) { try { @@ -746,19 +876,15 @@ public boolean start() { } } - _monitorExecutor.scheduleWithFixedDelay(new MonitorTask(), mgmtServiceConf.getPingInterval(), mgmtServiceConf.getPingInterval(), TimeUnit.SECONDS); - - final int agentConnectionsMonitorTimeInSecs = RemoteAgentNewConnectionsMonitorInterval.value(); - newAgentConnectionsMonitor.scheduleAtFixedRate(new AgentNewConnectionsMonitorTask(), agentConnectionsMonitorTimeInSecs, - agentConnectionsMonitorTimeInSecs, TimeUnit.SECONDS); - + initAndScheduleMonitorExecutor(); + initAndScheduleAgentConnectionsMonitor(); return true; } - public void startDirectlyConnectedHosts() { + public void startDirectlyConnectedHosts(final boolean forRebalance) { final List hosts = _resourceMgr.findDirectlyConnectedHosts(); for (final HostVO host : hosts) { - loadDirectlyConnectedHost(host, false); + loadDirectlyConnectedHost(host, forRebalance); } } @@ -770,25 +896,25 @@ private ServerResource loadResourcesWithoutHypervisor(final HostVO host) { final Constructor constructor = clazz.getConstructor(); resource = (ServerResource)constructor.newInstance(); } catch (final ClassNotFoundException e) { - logger.warn("Unable to find class " + host.getResource(), e); + logger.warn("Unable to find class {}", host.getResource(), e); } catch (final InstantiationException e) { - logger.warn("Unable to instantiate class " + host.getResource(), e); + logger.warn("Unable to instantiate class {}", host.getResource(), e); } catch (final IllegalAccessException e) { - logger.warn("Illegal access " + host.getResource(), e); + logger.warn("Illegal access {}", host.getResource(), e); } catch (final SecurityException e) { - logger.warn("Security error on " + host.getResource(), e); + logger.warn("Security error on {}", host.getResource(), e); } catch (final NoSuchMethodException e) { - logger.warn("NoSuchMethodException error on " + host.getResource(), e); + logger.warn("NoSuchMethodException error on {}", host.getResource(), e); } catch (final IllegalArgumentException e) { - logger.warn("IllegalArgumentException error on " + host.getResource(), e); + logger.warn("IllegalArgumentException error on {}", host.getResource(), e); } catch (final InvocationTargetException e) { - logger.warn("InvocationTargetException error on " + host.getResource(), e); + logger.warn("InvocationTargetException error on {}", host.getResource(), e); } if (resource != null) { _hostDao.loadDetails(host); - final HashMap params = new HashMap(host.getDetails().size() + 5); + final HashMap params = new HashMap<>(host.getDetails().size() + 5); params.putAll(host.getDetails()); params.put("guid", host.getGuid()); @@ -798,7 +924,7 @@ private ServerResource loadResourcesWithoutHypervisor(final HostVO host) { } if (host.getClusterId() != null) { params.put("cluster", Long.toString(host.getClusterId())); - String guid = null; + String guid; final ClusterVO cluster = _clusterDao.findById(host.getClusterId()); if (cluster.getGuid() == null) { guid = host.getDetail("pool"); @@ -833,8 +959,12 @@ public void rescan() { } protected boolean loadDirectlyConnectedHost(final HostVO host, final boolean forRebalance) { + return loadDirectlyConnectedHost(host, forRebalance, false); + } + + protected boolean loadDirectlyConnectedHost(final HostVO host, final boolean forRebalance, final boolean isTransferredConnection) { boolean initialized = false; - ServerResource resource = null; + ServerResource resource; try { // load the respective discoverer final Discoverer discoverer = _resourceMgr.getMatchingDiscover(host.getHypervisorType()); @@ -861,21 +991,21 @@ protected boolean loadDirectlyConnectedHost(final HostVO host, final boolean for if (forRebalance) { tapLoadingAgents(host.getId(), TapAgentsAction.Add); - final Host h = _resourceMgr.createHostAndAgent(host.getId(), resource, host.getDetails(), false, null, true); + final Host h = _resourceMgr.createHostAndAgent(host.getId(), resource, host.getDetails(), false, null, true, isTransferredConnection); tapLoadingAgents(host.getId(), TapAgentsAction.Del); - return h == null ? false : true; + return h != null; } else { _executor.execute(new SimulateStartTask(host.getId(), host.getUuid(), host.getName(), resource, host.getDetails())); return true; } } - protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) throws ConnectionException { + protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) { logger.debug("create DirectAgentAttache for {}", host); - final DirectAgentAttache attache = new DirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), resource, host.isInMaintenanceStates()); + final DirectAgentAttache attache = new DirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), resource, host.isInMaintenanceStates()); - AgentAttache old = null; + AgentAttache old; synchronized (_agents) { old = _agents.put(host.getId(), attache); } @@ -934,39 +1064,50 @@ protected Status getNextStatusOnDisconnection(Host host, final Status.Event even protected boolean handleDisconnectWithoutInvestigation(final AgentAttache attache, final Status.Event event, final boolean transitState, final boolean removeAgent) { final long hostId = attache.getId(); - + final HostVO host = _hostDao.findById(hostId); boolean result = false; GlobalLock joinLock = getHostJoinLock(hostId); - if (joinLock.lock(60)) { - try { - logger.info("Host {} is disconnecting with event {}", - attache, event); - Status nextStatus = null; - final HostVO host = _hostDao.findById(hostId); - if (host == null) { - logger.warn("Can't find host with {} ({})", hostId, attache); - nextStatus = Status.Removed; - } else { - nextStatus = getNextStatusOnDisconnection(host, event); - caService.purgeHostCertificate(host); - } - logger.debug("Deregistering link for {} with state {}", attache, nextStatus); - - removeAgent(attache, nextStatus); - - if (host != null && transitState) { - // update the state for host in DB as per the event - disconnectAgent(host, event, _nodeId); - } - } finally { - joinLock.unlock(); + try { + if (!joinLock.lock(60)) { + logger.debug("Unable to acquire lock on host {} to process agent disconnection", Objects.toString(host, String.valueOf(hostId))); + return result; } + + logger.debug("Acquired lock on host {}, to process agent disconnection", Objects.toString(host, String.valueOf(hostId))); + disconnectHostAgent(attache, event, host, transitState, joinLock); result = true; + } finally { + joinLock.releaseRef(); } - joinLock.releaseRef(); + return result; } + private void disconnectHostAgent(final AgentAttache attache, final Status.Event event, final HostVO host, final boolean transitState, final GlobalLock joinLock) { + try { + logger.info("Host {} is disconnecting with event {}", attache, event); + final long hostId = attache.getId(); + Status nextStatus; + if (host == null) { + logger.warn("Can't find host with {} ({})", hostId, attache); + nextStatus = Status.Removed; + } else { + nextStatus = getNextStatusOnDisconnection(host, event); + caService.purgeHostCertificate(host); + } + logger.debug("Deregistering link for {} with state {}", attache, nextStatus); + + removeAgent(attache, nextStatus); + + if (host != null && transitState) { + // update the state for host in DB as per the event + disconnectAgent(host, event, _nodeId); + } + } finally { + joinLock.unlock(); + } + } + protected boolean handleDisconnectWithInvestigation(final AgentAttache attache, Status.Event event) { final long hostId = attache.getId(); HostVO host = _hostDao.findById(hostId); @@ -1058,6 +1199,7 @@ protected boolean handleDisconnectWithInvestigation(final AgentAttache attache, host = _hostDao.findById(hostId); // Maybe the host magically reappeared? if (host != null && host.getStatus() == Status.Down) { _haMgr.scheduleRestartForVmsOnHost(host, true, HighAvailabilityManager.ReasonType.HostDown); + reconcileCommandService.updateReconcileCommandToInterruptedByHostId(hostId); } return true; } @@ -1076,7 +1218,7 @@ protected class DisconnectTask extends ManagedContextRunnable { @Override protected void runInContext() { try { - if (_investigate == true) { + if (_investigate) { handleDisconnectWithInvestigation(_attache, _event); } else { handleDisconnectWithoutInvestigation(_attache, _event, true, false); @@ -1128,8 +1270,8 @@ public Answer easySend(final Long hostId, final Command cmd) { public Answer[] send(final Long hostId, final Commands cmds) throws AgentUnavailableException, OperationTimedoutException { int wait = 0; if (cmds.size() > 1) { - logger.debug(String.format("Checking the wait time in seconds to be used for the following commands : %s. If there are multiple commands sent at once," + - "then max wait time of those will be used", cmds)); + logger.debug("Checking the wait time in seconds to be used for the following commands : {}. If there are multiple commands sent at once," + + "then max wait time of those will be used", cmds); } for (final Command cmd : cmds) { @@ -1192,7 +1334,7 @@ public void notifyMonitorsOfRemovedHost(long hostId, long clusterId) { public boolean executeUserRequest(final long hostId, final Event event) throws AgentUnavailableException { if (event == Event.AgentDisconnected) { - AgentAttache attache = null; + AgentAttache attache; attache = findAttache(hostId); logger.debug("Received agent disconnect event for host {} ({})", hostId, attache); if (attache != null) { @@ -1218,12 +1360,12 @@ public boolean isAgentAttached(final long hostId) { return agentAttache != null; } - protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) throws ConnectionException { + protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) { logger.debug("create ConnectedAgentAttache for {}", host); - final AgentAttache attache = new ConnectedAgentAttache(this, host.getId(), host.getUuid(), host.getName(), link, host.isInMaintenanceStates()); + final AgentAttache attache = new ConnectedAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), link, host.isInMaintenanceStates()); link.attach(attache); - AgentAttache old = null; + AgentAttache old; synchronized (_agents) { old = _agents.put(host.getId(), attache); } @@ -1234,47 +1376,62 @@ protected AgentAttache createAttacheForConnect(final HostVO host, final Link lin return attache; } - private AgentAttache sendReadyAndGetAttache(HostVO host, ReadyCommand ready, Link link, StartupCommand[] startup) throws ConnectionException { - final List agentMSHostList = new ArrayList<>(); - String lbAlgorithm = null; - if (startup != null && startup.length > 0) { - final String agentMSHosts = startup[0].getMsHostList(); - if (StringUtils.isNotEmpty(agentMSHosts)) { - String[] msHosts = agentMSHosts.split("@"); - if (msHosts.length > 1) { - lbAlgorithm = msHosts[1]; - } - agentMSHostList.addAll(Arrays.asList(msHosts[0].split(","))); + private AgentAttache sendReadyAndGetAttache(HostVO host, ReadyCommand ready, Link link, StartupCommand[] startupCmds) throws ConnectionException { + AgentAttache attache; + GlobalLock joinLock = getHostJoinLock(host.getId()); + try { + if (!joinLock.lock(60)) { + throw new ConnectionException(true, String.format("Unable to acquire lock on host %s, to process agent connection", host)); } + + logger.debug("Acquired lock on host {}, to process agent connection", host); + attache = connectHostAgent(host, ready, link, startupCmds, joinLock); + } finally { + joinLock.releaseRef(); } - ready.setArch(host.getArch().getType()); - AgentAttache attache = null; - GlobalLock joinLock = getHostJoinLock(host.getId()); - if (joinLock.lock(60)) { - try { - if (!indirectAgentLB.compareManagementServerList(host.getId(), host.getDataCenterId(), agentMSHostList, lbAlgorithm)) { - final List newMSList = indirectAgentLB.getManagementServerList(host.getId(), host.getDataCenterId(), null); - ready.setMsHostList(newMSList); - ready.setLbAlgorithm(indirectAgentLB.getLBAlgorithmName()); - ready.setLbCheckInterval(indirectAgentLB.getLBPreferredHostCheckInterval(host.getClusterId())); - logger.debug("Agent's management server host list is not up to date, sending list update: {}", newMSList); + return attache; + } + + private AgentAttache connectHostAgent(HostVO host, ReadyCommand ready, Link link, StartupCommand[] startupCmds, GlobalLock joinLock) throws ConnectionException { + AgentAttache attache; + try { + final List agentMSHostList = new ArrayList<>(); + String lbAlgorithm = null; + if (startupCmds != null && startupCmds.length > 0) { + final String agentMSHosts = startupCmds[0].getMsHostList(); + if (StringUtils.isNotEmpty(agentMSHosts)) { + String[] msHosts = agentMSHosts.split("@"); + if (msHosts.length > 1) { + lbAlgorithm = msHosts[1]; + } + agentMSHostList.addAll(Arrays.asList(msHosts[0].split(","))); } + } - attache = createAttacheForConnect(host, link); - attache = notifyMonitorsOfConnection(attache, startup, false); - } finally { - joinLock.unlock(); + if (!indirectAgentLB.compareManagementServerListAndLBAlgorithm(host.getId(), host.getDataCenterId(), agentMSHostList, lbAlgorithm)) { + final List newMSList = indirectAgentLB.getManagementServerList(host.getId(), host.getDataCenterId(), null); + ready.setMsHostList(newMSList); + String newLBAlgorithm = indirectAgentLB.getLBAlgorithmName(); + ready.setLbAlgorithm(newLBAlgorithm); + logger.debug("Agent's management server host list or lb algorithm is not up to date, sending list and algorithm update: {}, {}", newMSList, newLBAlgorithm); } - } else { - throw new ConnectionException(true, - String.format("Unable to acquire lock on host %s", host)); + + final List avoidMsList = _mshostDao.listNonUpStateMsIPs(); + ready.setAvoidMsHostList(avoidMsList); + ready.setLbCheckInterval(indirectAgentLB.getLBPreferredHostCheckInterval(host.getClusterId())); + ready.setArch(host.getArch().getType()); + + attache = createAttacheForConnect(host, link); + attache = notifyMonitorsOfConnection(attache, startupCmds, false); + } finally { + joinLock.unlock(); } - joinLock.releaseRef(); + return attache; } - private AgentAttache handleConnectedAgent(final Link link, final StartupCommand[] startup, final Request request) { + private AgentAttache handleConnectedAgent(final Link link, final StartupCommand[] startup) { AgentAttache attache = null; ReadyCommand ready = null; try { @@ -1302,7 +1459,7 @@ private AgentAttache handleConnectedAgent(final Link link, final StartupCommand[ easySend(attache.getId(), ready); } } catch (final Exception e) { - logger.debug("Failed to send ready command:" + e.toString()); + logger.debug("Failed to send ready command:", e); } return attache; } @@ -1328,6 +1485,8 @@ public SimulateStartTask(final long id, String uuid, String name, final ServerRe this.id = id; this.resource = resource; this.details = details; + this.uuid = uuid; + this.name = name; } @Override @@ -1376,7 +1535,7 @@ protected void runInContext() { startups[i] = (StartupCommand)_cmds[i]; } - final AgentAttache attache = handleConnectedAgent(_link, startups, _request); + final AgentAttache attache = handleConnectedAgent(_link, startups); if (attache == null) { logger.warn("Unable to create attache for agent: {}", _request); } @@ -1397,7 +1556,7 @@ protected void connectAgent(final Link link, final Command[] cmds, final Request break; } } - Response response = null; + Response response; response = new Response(request, answers[0], _nodeId, -1); try { link.send(response.toBytes()); @@ -1478,7 +1637,6 @@ protected void processRequest(final Link link, final Request request) { } final long hostId = attache.getId(); - final String hostName = attache.getName(); if (logger.isDebugEnabled()) { if (cmd instanceof PingRoutingCommand) { @@ -1497,7 +1655,7 @@ protected void processRequest(final Link link, final Request request) { final Answer[] answers = new Answer[cmds.length]; for (int i = 0; i < cmds.length; i++) { cmd = cmds[i]; - Answer answer = null; + Answer answer; try { if (cmd instanceof StartupRoutingCommand) { final StartupRoutingCommand startup = (StartupRoutingCommand) cmd; @@ -1517,7 +1675,6 @@ protected void processRequest(final Link link, final Request request) { final String reason = shutdown.getReason(); logger.info("Host {} has informed us that it is shutting down with reason {} and detail {}", attache, reason, shutdown.getDetail()); if (reason.equals(ShutdownCommand.Update)) { - // disconnectWithoutInvestigation(attache, Event.UpdateNeeded); throw new CloudRuntimeException("Agent update not implemented"); } else if (reason.equals(ShutdownCommand.Requested)) { disconnectWithoutInvestigation(attache, Event.ShutdownRequested); @@ -1531,7 +1688,7 @@ protected void processRequest(final Link link, final Request request) { final long cmdHostId = ((PingCommand)cmd).getHostId(); boolean requestStartupCommand = false; - final HostVO host = _hostDao.findById(Long.valueOf(cmdHostId)); + final HostVO host = _hostDao.findById(cmdHostId); boolean gatewayAccessible = true; // if the router is sending a ping, verify the // gateway was pingable @@ -1556,10 +1713,14 @@ protected void processRequest(final Link link, final Request request) { logger.debug("Not processing {} for agent id={}; can't find the host in the DB", PingRoutingCommand.class.getSimpleName(), cmdHostId); } } - if (host!= null && host.getStatus() != Status.Up && gatewayAccessible) { + if (host != null && host.getStatus() != Status.Up && gatewayAccessible) { requestStartupCommand = true; } - answer = new PingAnswer((PingCommand)cmd, requestStartupCommand); + final List avoidMsList = _mshostDao.listNonUpStateMsIPs(); + answer = new PingAnswer((PingCommand)cmd, avoidMsList, requestStartupCommand); + + // Add or update reconcile tasks + reconcileCommandService.processCommand(cmd, answer); } else if (cmd instanceof ReadyAnswer) { final HostVO host = _hostDao.findById(attache.getId()); if (host == null) { @@ -1581,7 +1742,7 @@ protected void processRequest(final Link link, final Request request) { if (logD) { logger.debug("SeqA {}-: Sending {}", attache.getId(), response.getSequence(), response); } else { - logger.trace("SeqA {}-: Sending {}" + attache.getId(), response.getSequence(), response); + logger.trace("SeqA {}-: Sending {} {}", response.getSequence(), response, attache.getId()); } try { link.send(response.toBytes()); @@ -1601,30 +1762,28 @@ protected void processResponse(final Link link, final Response response) { @Override protected void doTask(final Task task) throws TaskExecutionException { - final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB); - try { + try (TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB)) { final Type type = task.getType(); - if (type == Task.Type.DATA) { + if (type == Type.DATA) { final byte[] data = task.getData(); try { final Request event = Request.parse(data); if (event instanceof Response) { - processResponse(task.getLink(), (Response)event); + processResponse(task.getLink(), (Response) event); } else { processRequest(task.getLink(), event); } } catch (final UnsupportedVersionException e) { logger.warn(e.getMessage()); - // upgradeAgent(task.getLink(), data, e.getReason()); } catch (final ClassNotFoundException e) { final String message = String.format("Exception occurred when executing tasks! Error '%s'", e.getMessage()); logger.error(message); throw new TaskExecutionException(message, e); } - } else if (type == Task.Type.CONNECT) { - } else if (type == Task.Type.DISCONNECT) { + } else if (type == Type.CONNECT) { + } else if (type == Type.DISCONNECT) { final Link link = task.getLink(); - final AgentAttache attache = (AgentAttache)link.attachment(); + final AgentAttache attache = (AgentAttache) link.attachment(); if (attache != null) { disconnectWithInvestigation(attache, Event.AgentDisconnected); } else { @@ -1633,8 +1792,6 @@ protected void doTask(final Task task) throws TaskExecutionException { link.terminated(); } } - } finally { - txn.close(); } } } @@ -1710,11 +1867,11 @@ protected boolean isHostOwnerSwitched(final HostVO host) { return false; } - private void disconnectInternal(final long hostId, final Status.Event event, final boolean invstigate) { + private void disconnectInternal(final long hostId, final Status.Event event, final boolean investigate) { final AgentAttache attache = findAttache(hostId); if (attache != null) { - if (!invstigate) { + if (!investigate) { disconnectWithoutInvestigation(attache, event); } else { disconnectWithInvestigation(attache, event); @@ -1861,7 +2018,7 @@ protected void runInContext() { } protected List findAgentsBehindOnPing() { - final List agentsBehind = new ArrayList(); + final List agentsBehind = new ArrayList<>(); final long cutoffTime = InaccurateClock.getTimeInSeconds() - mgmtServiceConf.getTimeout(); for (final Map.Entry entry : _pingMap.entrySet()) { if (entry.getValue() < cutoffTime) { @@ -1869,7 +2026,7 @@ protected List findAgentsBehindOnPing() { } } - if (agentsBehind.size() > 0) { + if (!agentsBehind.isEmpty()) { logger.info("Found the following agents behind on ping: {}", agentsBehind); } @@ -2007,12 +2164,16 @@ public void processHostAdded(long hostId) { @Override public void processConnect(final Host host, final StartupCommand cmd, final boolean forRebalance) { - if (cmd instanceof StartupRoutingCommand) { - if (((StartupRoutingCommand)cmd).getHypervisorType() == HypervisorType.KVM || ((StartupRoutingCommand)cmd).getHypervisorType() == HypervisorType.LXC) { - Map params = new HashMap(); - params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString())); - params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString())); - params.put(NetworkOrchestrationService.TUNGSTEN_ENABLED.key(), String.valueOf(NetworkOrchestrationService.TUNGSTEN_ENABLED.valueIn(host.getDataCenterId()))); + if (!(cmd instanceof StartupRoutingCommand) || cmd.isConnectionTransferred()) { + return; + } + + if (((StartupRoutingCommand)cmd).getHypervisorType() == HypervisorType.KVM || ((StartupRoutingCommand)cmd).getHypervisorType() == HypervisorType.LXC) { + Map params = new HashMap<>(); + params.put(Config.RouterAggregationCommandEachTimeout.toString(), _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString())); + params.put(Config.MigrateWait.toString(), _configDao.getValue(Config.MigrateWait.toString())); + params.put(NetworkOrchestrationService.TUNGSTEN_ENABLED.key(), String.valueOf(NetworkOrchestrationService.TUNGSTEN_ENABLED.valueIn(host.getDataCenterId()))); + params.put(ReconcileCommandService.ReconcileCommandsEnabled.key(), String.valueOf(_reconcileCommandsEnabled)); try { SetHostParamsCommand cmds = new SetHostParamsCommand(params); @@ -2024,8 +2185,6 @@ public void processConnect(final Host host, final StartupCommand cmd, final bool } } - } - @Override public boolean processDisconnect(final long agentId, final Status state) { return true; @@ -2056,13 +2215,13 @@ protected Map> getHostsPerZone() { if (allHosts == null) { return null; } - Map> hostsByZone = new HashMap>(); + Map> hostsByZone = new HashMap<>(); for (HostVO host : allHosts) { if (host.getHypervisorType() == HypervisorType.KVM || host.getHypervisorType() == HypervisorType.LXC) { Long zoneId = host.getDataCenterId(); List hostIds = hostsByZone.get(zoneId); if (hostIds == null) { - hostIds = new ArrayList(); + hostIds = new ArrayList<>(); } hostIds.add(host.getId()); hostsByZone.put(zoneId, hostIds); @@ -2093,6 +2252,11 @@ public void propagateChangeToAgents(Map params) { } } + @Override + public boolean transferDirectAgentsFromMS(String fromMsUuid, long fromMsId, long timeoutDurationInMs, boolean excludeHostsInMaintenance) { + return true; + } + @Override public int getHostSshPort(HostVO host) { if (host == null) { @@ -2115,4 +2279,36 @@ public int getHostSshPort(HostVO host) { private GlobalLock getHostJoinLock(Long hostId) { return GlobalLock.getInternLock(String.format("%s-%s", "Host-Join", hostId)); } + + public boolean isReconcileCommandsEnabled(HypervisorType hypervisorType) { + return _reconcileCommandsEnabled && ReconcileCommandService.SupportedHypervisorTypes.contains(hypervisorType); + } + + public void updateReconcileCommandsIfNeeded(long requestSeq, Command[] commands, Command.State state) { + if (!_reconcileCommandsEnabled) { + return; + } + for (Command command: commands) { + if (command.isReconcile()) { + reconcileCommandService.updateReconcileCommand(requestSeq, command, null, state, null); + } + } + } + + public Pair getStateAndAnswerOfReconcileCommand(long requestSeq, Command command) { + ReconcileCommandVO reconcileCommandVO = reconcileCommandDao.findCommand(requestSeq, command.toString()); + if (reconcileCommandVO == null) { + return null; + } + Command.State state = reconcileCommandVO.getStateByAgent(); + if (reconcileCommandVO.getAnswerName() == null || reconcileCommandVO.getAnswerInfo() == null) { + return new Pair<>(state, null); + } + Answer answer = ReconcileCommandUtils.parseAnswerFromAnswerInfo(reconcileCommandVO.getAnswerName(), reconcileCommandVO.getAnswerInfo()); + return new Pair<>(state, answer); + } + + public Integer getReconcileInterval() { + return _reconcileCommandInterval; + } } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentAttache.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentAttache.java index e36b145c8bca..5e4ccfa67c6f 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentAttache.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentAttache.java @@ -31,6 +31,7 @@ import com.cloud.agent.transport.Request; import com.cloud.exception.AgentUnavailableException; import com.cloud.host.Status; +import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.nio.Link; public class ClusteredAgentAttache extends ConnectedAgentAttache implements Routable { @@ -44,14 +45,14 @@ static public void initialize(final ClusteredAgentManagerImpl agentMgr) { s_clusteredAgentMgr = agentMgr; } - public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name) { - super(agentMgr, id, uuid, name, null, false); + public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Hypervisor.HypervisorType hypervisorType) { + super(agentMgr, id, uuid, name, hypervisorType, null, false); _forward = true; _transferRequests = new LinkedList(); } - public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Link link, final boolean maintenance) { - super(agentMgr, id, uuid, name, link, maintenance); + public ClusteredAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Hypervisor.HypervisorType hypervisorType, final Link link, final boolean maintenance) { + super(agentMgr, id, uuid, name, hypervisorType, link, maintenance); _forward = link == null; _transferRequests = new LinkedList(); } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java index eed1aab00543..38a198b73040 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredAgentManagerImpl.java @@ -42,22 +42,28 @@ import javax.net.ssl.SSLContext; import javax.net.ssl.SSLEngine; +import com.cloud.resource.ResourceState; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.extensions.command.ExtensionServerActionBaseCommand; +import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; import org.apache.cloudstack.ha.dao.HAConfigDao; +import org.apache.cloudstack.maintenance.ManagementServerMaintenanceManager; +import org.apache.cloudstack.maintenance.command.BaseShutdownManagementServerHostCommand; +import org.apache.cloudstack.maintenance.command.CancelMaintenanceManagementServerHostCommand; +import org.apache.cloudstack.maintenance.command.CancelShutdownManagementServerHostCommand; +import org.apache.cloudstack.maintenance.command.PrepareForMaintenanceManagementServerHostCommand; +import org.apache.cloudstack.maintenance.command.PrepareForShutdownManagementServerHostCommand; +import org.apache.cloudstack.maintenance.command.TriggerShutdownManagementServerHostCommand; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.managed.context.ManagedContextTimerTask; import org.apache.cloudstack.management.ManagementServerHost; import org.apache.cloudstack.outofbandmanagement.dao.OutOfBandManagementDao; -import org.apache.cloudstack.shutdown.ShutdownManager; -import org.apache.cloudstack.shutdown.command.BaseShutdownManagementServerHostCommand; -import org.apache.cloudstack.shutdown.command.CancelShutdownManagementServerHostCommand; -import org.apache.cloudstack.shutdown.command.PrepareForShutdownManagementServerHostCommand; -import org.apache.cloudstack.shutdown.command.TriggerShutdownManagementServerHostCommand; import org.apache.cloudstack.utils.identity.ManagementServerNode; import org.apache.cloudstack.utils.security.SSLUtils; +import org.apache.commons.collections.CollectionUtils; import com.cloud.agent.api.Answer; import com.cloud.agent.api.CancelCommand; @@ -80,6 +86,9 @@ import com.cloud.cluster.agentlb.HostTransferMapVO.HostTransferState; import com.cloud.cluster.agentlb.dao.HostTransferMapDao; import com.cloud.cluster.dao.ManagementServerHostDao; +import com.cloud.cluster.dao.ManagementServerHostPeerDao; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.dao.DataCenterDao; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.OperationTimedoutException; import com.cloud.exception.UnsupportedVersionException; @@ -101,24 +110,27 @@ import com.google.gson.Gson; public class ClusteredAgentManagerImpl extends AgentManagerImpl implements ClusterManagerListener, ClusteredAgentRebalanceService { - private static final ScheduledExecutorService s_transferExecutor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("Cluster-AgentRebalancingExecutor")); + private static ScheduledExecutorService s_transferExecutor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("Cluster-AgentRebalancingExecutor")); private final long rebalanceTimeOut = 300000; // 5 mins - after this time remove the agent from the transfer list public final static long STARTUP_DELAY = 5000; public final static long SCAN_INTERVAL = 90000; // 90 seconds, it takes 60 sec for xenserver to fail login public final static int ACQUIRE_GLOBAL_LOCK_TIMEOUT_FOR_COOPERATION = 5; // 5 seconds - protected Set _agentToTransferIds = new HashSet(); + protected Set _agentToTransferIds = new HashSet<>(); Gson _gson; protected HashMap _peers; protected HashMap _sslEngines; private final Timer _timer = new Timer("ClusteredAgentManager Timer"); boolean _agentLbHappened = false; + private int _mshostCounter = 0; @Inject protected ClusterManager _clusterMgr = null; @Inject protected ManagementServerHostDao _mshostDao; @Inject + protected ManagementServerHostPeerDao _mshostPeerDao; + @Inject protected HostTransferMapDao _hostTransferDao; @Inject protected List _lbPlanners; @@ -133,23 +145,27 @@ public class ClusteredAgentManagerImpl extends AgentManagerImpl implements Clust @Inject private CAManager caService; @Inject - private ShutdownManager shutdownManager; + private ManagementServerMaintenanceManager managementServerMaintenanceManager; + @Inject + private DataCenterDao dcDao; + @Inject + ExtensionsManager extensionsManager; protected ClusteredAgentManagerImpl() { super(); } - protected final ConfigKey EnableLB = new ConfigKey(Boolean.class, "agent.lb.enabled", "Advanced", "false", "Enable agent load balancing between management server nodes", true); - protected final ConfigKey ConnectedAgentThreshold = new ConfigKey(Double.class, "agent.load.threshold", "Advanced", "0.7", - "What percentage of the agents can be held by one management server before load balancing happens", true, EnableLB.key()); - protected final ConfigKey LoadSize = new ConfigKey(Integer.class, "direct.agent.load.size", "Advanced", "16", "How many agents to connect to in each round", true); - protected final ConfigKey ScanInterval = new ConfigKey(Integer.class, "direct.agent.scan.interval", "Advanced", "90", "Interval between scans to load agents", false, + protected final ConfigKey EnableLB = new ConfigKey<>(Boolean.class, "agent.lb.enabled", "Advanced", "false", "Enable direct agents load balancing between management server nodes", true); + protected final ConfigKey ConnectedAgentThreshold = new ConfigKey<>(Double.class, "agent.load.threshold", "Advanced", "0.7", + "What percentage of the direct agents can be held by one management server before load balancing happens", true, EnableLB.key()); + protected final ConfigKey LoadSize = new ConfigKey<>(Integer.class, "direct.agent.load.size", "Advanced", "16", "How many direct agents to connect to in each round", true); + protected final ConfigKey ScanInterval = new ConfigKey<>(Integer.class, "direct.agent.scan.interval", "Advanced", "90", "Interval between scans to load direct agents", false, ConfigKey.Scope.Global, 1000); @Override public boolean configure(final String name, final Map xmlParams) throws ConfigurationException { - _peers = new HashMap(7); - _sslEngines = new HashMap(7); + _peers = new HashMap<>(7); + _sslEngines = new HashMap<>(7); _nodeId = ManagementServerNode.getManagementServerId(); logger.info("Configuring ClusterAgentManagerImpl. management server node id(msid): {}", _nodeId); @@ -172,6 +188,13 @@ public boolean start() { _timer.schedule(new DirectAgentScanTimerTask(), STARTUP_DELAY, ScanInterval.value()); logger.debug("Scheduled direct agent scan task to run at an interval of {} seconds", ScanInterval.value()); + ManagementServerHostVO msHost = _mshostDao.findByMsid(_nodeId); + if (msHost != null && (ManagementServerHost.State.Maintenance.equals(msHost.getState()) || ManagementServerHost.State.PreparingForMaintenance.equals(msHost.getState()))) { + s_transferExecutor.shutdownNow(); + cleanupTransferMap(_nodeId); + return true; + } + // Schedule tasks for agent rebalancing if (isAgentRebalanceEnabled()) { cleanupTransferMap(_nodeId); @@ -201,7 +224,7 @@ protected void scanDirectAgentToLoad() { if (hosts != null) { hosts.addAll(appliances); - if (hosts.size() > 0) { + if (!hosts.isEmpty()) { logger.debug("Found {} unmanaged direct hosts, processing connect for them...", hosts.size()); for (final HostVO host : hosts) { try { @@ -255,8 +278,8 @@ public Task create(final Task.Type type, final Link link, final byte[] data) { protected AgentAttache createAttache(final HostVO host) { logger.debug("create forwarding ClusteredAgentAttache for {}", host); long id = host.getId(); - final AgentAttache attache = new ClusteredAgentAttache(this, id, host.getUuid(), host.getName()); - AgentAttache old = null; + final AgentAttache attache = new ClusteredAgentAttache(this, id, host.getUuid(), host.getName(), host.getHypervisorType()); + AgentAttache old; synchronized (_agents) { old = _agents.get(host.getId()); _agents.put(host.getId(), attache); @@ -271,9 +294,9 @@ protected AgentAttache createAttache(final HostVO host) { @Override protected AgentAttache createAttacheForConnect(final HostVO host, final Link link) { logger.debug("create ClusteredAgentAttache for {}", host); - final AgentAttache attache = new ClusteredAgentAttache(this, host.getId(), host.getUuid(), host.getName(), link, host.isInMaintenanceStates()); + final AgentAttache attache = new ClusteredAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), link, host.isInMaintenanceStates()); link.attach(attache); - AgentAttache old = null; + AgentAttache old; synchronized (_agents) { old = _agents.get(host.getId()); _agents.put(host.getId(), attache); @@ -287,8 +310,8 @@ protected AgentAttache createAttacheForConnect(final HostVO host, final Link lin @Override protected AgentAttache createAttacheForDirectConnect(final Host host, final ServerResource resource) { logger.debug("Create ClusteredDirectAgentAttache for {}.", host); - final DirectAgentAttache attache = new ClusteredDirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), _nodeId, resource, host.isInMaintenanceStates()); - AgentAttache old = null; + final DirectAgentAttache attache = new ClusteredDirectAgentAttache(this, host.getId(), host.getUuid(), host.getName(), host.getHypervisorType(), _nodeId, resource, host.isInMaintenanceStates()); + AgentAttache old; synchronized (_agents) { old = _agents.get(host.getId()); _agents.put(host.getId(), attache); @@ -407,15 +430,15 @@ protected void logI(final byte[] bytes, final String msg) { public boolean routeToPeer(final String peer, final byte[] bytes) { int i = 0; SocketChannel ch = null; - SSLEngine sslEngine = null; + SSLEngine sslEngine; while (i++ < 5) { ch = connectToPeer(peer, ch); if (ch == null) { try { - logD(bytes, "Unable to route to peer: " + Request.parse(bytes).toString()); + logD(bytes, "Unable to establish connection to route to peer: " + Request.parse(bytes)); } catch (ClassNotFoundException | UnsupportedVersionException e) { // Request.parse thrown exception when we try to log it, log as much as we can - logD(bytes, "Unable to route to peer, and Request.parse further caught exception" + e.getMessage()); + logD(bytes, "Unable to establish connection to route to peer, and Request.parse further caught exception" + e.getMessage()); } return false; } @@ -430,7 +453,7 @@ public boolean routeToPeer(final String peer, final byte[] bytes) { return true; } catch (final IOException e) { try { - logI(bytes, "Unable to route to peer: " + Request.parse(bytes).toString() + " due to " + e.getMessage()); + logI(bytes, "Unable to route to peer: " + Request.parse(bytes) + " due to " + e.getMessage()); } catch (ClassNotFoundException | UnsupportedVersionException ex) { // Request.parse thrown exception when we try to log it, log as much as we can logI(bytes, "Unable to route to peer due to" + e.getMessage() + ". Also caught exception when parsing request: " + ex.getMessage()); @@ -473,7 +496,7 @@ public void closePeer(final String peerName) { public SocketChannel connectToPeer(final String peerName, final SocketChannel prevCh) { synchronized (_peers) { final SocketChannel ch = _peers.get(peerName); - SSLEngine sslEngine = null; + SSLEngine sslEngine; if (prevCh != null) { try { prevCh.close(); @@ -593,7 +616,7 @@ public boolean stop() { } @Override - public void startDirectlyConnectedHosts() { + public void startDirectlyConnectedHosts(final boolean forRebalance) { // override and let it be dummy for purpose, we will scan and load direct agents periodically. // We may also pickup agents that have been left over from other crashed management server } @@ -606,9 +629,8 @@ public ClusteredAgentHandler(final Task.Type type, final Link link, final byte[] @Override protected void doTask(final Task task) throws TaskExecutionException { - final TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB); - try { - if (task.getType() != Task.Type.DATA) { + try (TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.CLOUD_DB)) { + if (task.getType() != Type.DATA) { super.doTask(task); return; } @@ -625,7 +647,6 @@ protected void doTask(final Task task) throws TaskExecutionException { final Link link = task.getLink(); if (Request.fromServer(data)) { - final AgentAttache agent = findAttache(hostId); if (Request.isControl(data)) { @@ -659,7 +680,6 @@ protected void doTask(final Task task) throws TaskExecutionException { // to deserialize this and send it through the agent attache. final Request req = Request.parse(data); agent.send(req, null); - return; } else { if (agent instanceof Routable) { final Routable cluster = (Routable) agent; @@ -674,7 +694,6 @@ protected void doTask(final Task task) throws TaskExecutionException { cancel(Long.toString(Request.getManagementServerId(data)), hostId, Request.getSequence(data), e.getMessage()); } } else { - final long mgmtId = Request.getManagementServerId(data); if (mgmtId != -1 && mgmtId != _nodeId) { routeToPeer(Long.toString(mgmtId), data); @@ -683,9 +702,8 @@ protected void doTask(final Task task) throws TaskExecutionException { if (attache != null) { attache.sendNext(Request.getSequence(data)); } - logD(data, "No attache to process " + Request.parse(data).toString()); + logD(data, "No attache to process " + Request.parse(data)); } - return; } else { if (Request.isRequest(data)) { super.doTask(task); @@ -701,7 +719,6 @@ protected void doTask(final Task task) throws TaskExecutionException { logger.info("SeqA {}-{}: Response is not processed: {}", attache.getId(), response.getSequence(), response.toString()); } } - return; } } } catch (final ClassNotFoundException e) { @@ -712,8 +729,6 @@ protected void doTask(final Task task) throws TaskExecutionException { final String message = String.format("UnsupportedVersionException occurred when executing tasks! Error '%s'", e.getMessage()); logger.error(message); throw new TaskExecutionException(message, e); - } finally { - txn.close(); } } } @@ -750,12 +765,17 @@ public void removeAgent(final AgentAttache attache, final Status nextState) { @Override public boolean executeRebalanceRequest(final long agentId, final long currentOwnerId, final long futureOwnerId, final Event event) throws AgentUnavailableException, OperationTimedoutException { + return executeRebalanceRequest(agentId, currentOwnerId, futureOwnerId, event, false); + } + + @Override + public boolean executeRebalanceRequest(final long agentId, final long currentOwnerId, final long futureOwnerId, final Event event, boolean isConnectionTransfer) throws AgentUnavailableException, OperationTimedoutException { boolean result = false; if (event == Event.RequestAgentRebalance) { - return setToWaitForRebalance(agentId, currentOwnerId, futureOwnerId); + return setToWaitForRebalance(agentId); } else if (event == Event.StartAgentRebalance) { try { - result = rebalanceHost(agentId, currentOwnerId, futureOwnerId); + result = rebalanceHost(agentId, currentOwnerId, futureOwnerId, isConnectionTransfer); } catch (final Exception e) { logger.warn("Unable to rebalance host id={} ({})", agentId, findAttache(agentId), e); } @@ -807,7 +827,7 @@ public void startRebalanceAgents() { sc.and(sc.entity().getType(), Op.EQ, Host.Type.Routing); final List allManagedAgents = sc.list(); - int avLoad = 0; + int avLoad; if (!allManagedAgents.isEmpty() && !allMS.isEmpty()) { avLoad = allManagedAgents.size() / allMS.size(); @@ -825,7 +845,7 @@ public void startRebalanceAgents() { for (final ManagementServerHostVO node : allMS) { if (node.getMsid() != _nodeId) { - List hostsToRebalance = new ArrayList(); + List hostsToRebalance = new ArrayList<>(); for (final AgentLoadBalancerPlanner lbPlanner : _lbPlanners) { hostsToRebalance = lbPlanner.getHostsToRebalance(node, avLoad); if (hostsToRebalance != null && !hostsToRebalance.isEmpty()) { @@ -851,7 +871,7 @@ public void startRebalanceAgents() { HostTransferMapVO transfer = null; try { transfer = _hostTransferDao.startAgentTransfering(hostId, node.getMsid(), _nodeId); - final Answer[] answer = sendRebalanceCommand(node.getMsid(), hostId, node.getMsid(), _nodeId, Event.RequestAgentRebalance); + final Answer[] answer = sendRebalanceCommand(node.getMsid(), hostId, node.getMsid(), _nodeId); if (answer == null) { logger.warn("Failed to get host {} from management server {}", host, node); result = false; @@ -878,8 +898,12 @@ public void startRebalanceAgents() { } } - private Answer[] sendRebalanceCommand(final long peer, final long agentId, final long currentOwnerId, final long futureOwnerId, final Event event) { - final TransferAgentCommand transfer = new TransferAgentCommand(agentId, currentOwnerId, futureOwnerId, event); + private Answer[] sendRebalanceCommand(final long peer, final long agentId, final long currentOwnerId, final long futureOwnerId) { + return sendRebalanceCommand(peer, agentId, currentOwnerId, futureOwnerId, Event.RequestAgentRebalance, false); + } + + private Answer[] sendRebalanceCommand(final long peer, final long agentId, final long currentOwnerId, final long futureOwnerId, final Event event, final boolean isConnectionTransfer) { + final TransferAgentCommand transfer = new TransferAgentCommand(agentId, currentOwnerId, futureOwnerId, event, isConnectionTransfer); final Commands commands = new Commands(Command.OnError.Stop); commands.addCommand(transfer); @@ -890,8 +914,7 @@ private Answer[] sendRebalanceCommand(final long peer, final long agentId, final final String peerName = Long.toString(peer); final String cmdStr = _gson.toJson(cmds); final String ansStr = _clusterMgr.execute(peerName, agentId, cmdStr, true); - final Answer[] answers = _gson.fromJson(ansStr, Answer[].class); - return answers; + return _gson.fromJson(ansStr, Answer[].class); } catch (final Exception e) { logger.warn("Caught exception while talking to {}", currentOwnerId, e); return null; @@ -940,9 +963,8 @@ protected void runInContext() { try { logger.trace("Clustered agent transfer scan check, management server id: {}", _nodeId); synchronized (_agentToTransferIds) { - if (_agentToTransferIds.size() > 0) { + if (!_agentToTransferIds.isEmpty()) { logger.debug("Found {} agents to transfer", _agentToTransferIds.size()); - // for (Long hostId : _agentToTransferIds) { for (final Iterator iterator = _agentToTransferIds.iterator(); iterator.hasNext(); ) { final Long hostId = iterator.next(); final AgentAttache attache = findAttache(hostId); @@ -964,7 +986,7 @@ protected void runInContext() { } if (transferMap.getInitialOwner() != _nodeId || attache == null || attache.forForward()) { - logger.debug(String.format("Management server %d doesn't own host id=%d (%s) any more, skipping rebalance for the host", _nodeId, hostId, attache)); + logger.debug("Management server {} doesn't own host id={} ({}) any more, skipping rebalance for the host", _nodeId, hostId, attache); iterator.remove(); _hostTransferDao.completeAgentTransfer(hostId); continue; @@ -984,9 +1006,7 @@ protected void runInContext() { _executor.execute(new RebalanceTask(hostId, transferMap.getInitialOwner(), transferMap.getFutureOwner())); } catch (final RejectedExecutionException ex) { logger.warn("Failed to submit rebalance task for host id={} ({}); postponing the execution", hostId, attache); - continue; } - } else { logger.debug("Agent {} ({}) can't be transferred yet as its request queue size is {} and listener queue size is {}", hostId, attache, attache.getQueueSize(), attache.getNonRecurringListenersSize()); @@ -996,7 +1016,6 @@ protected void runInContext() { logger.trace("Found no agents to be transferred by the management server {}", _nodeId); } } - } catch (final Throwable e) { logger.error("Problem with the clustered agent transfer scan check!", e); } @@ -1004,7 +1023,7 @@ protected void runInContext() { }; } - private boolean setToWaitForRebalance(final long hostId, final long currentOwnerId, final long futureOwnerId) { + private boolean setToWaitForRebalance(final long hostId) { logger.debug("Adding agent {} ({}) to the list of agents to transfer", hostId, findAttache(hostId)); synchronized (_agentToTransferIds) { return _agentToTransferIds.add(hostId); @@ -1012,7 +1031,10 @@ private boolean setToWaitForRebalance(final long hostId, final long currentOwner } protected boolean rebalanceHost(final long hostId, final long currentOwnerId, final long futureOwnerId) throws AgentUnavailableException { + return rebalanceHost(hostId, currentOwnerId, futureOwnerId, false); + } + protected boolean rebalanceHost(final long hostId, final long currentOwnerId, final long futureOwnerId, final boolean isConnectionTransfer) throws AgentUnavailableException { boolean result = true; if (currentOwnerId == _nodeId) { if (!startRebalance(hostId)) { @@ -1021,7 +1043,7 @@ protected boolean rebalanceHost(final long hostId, final long currentOwnerId, fi return false; } try { - final Answer[] answer = sendRebalanceCommand(futureOwnerId, hostId, currentOwnerId, futureOwnerId, Event.StartAgentRebalance); + final Answer[] answer = sendRebalanceCommand(futureOwnerId, hostId, currentOwnerId, futureOwnerId, Event.StartAgentRebalance, isConnectionTransfer); if (answer == null || !answer[0].getResult()) { result = false; } @@ -1050,8 +1072,8 @@ protected boolean rebalanceHost(final long hostId, final long currentOwnerId, fi } if (result) { - logger.debug("Loading directly connected {} to the management server {} as a part of rebalance process", host, _nodeId); - result = loadDirectlyConnectedHost(host, true); + logger.debug("Loading directly connected host {} to the management server {} as a part of rebalance process", host, _nodeId); + result = loadDirectlyConnectedHost(host, true, isConnectionTransfer); } else { logger.warn("Failed to disconnect {} as a part of rebalance process without notification", host); } @@ -1073,12 +1095,12 @@ protected boolean rebalanceHost(final long hostId, final long currentOwnerId, fi protected void finishRebalance(final long hostId, final long futureOwnerId, final Event event) { - final boolean success = event == Event.RebalanceCompleted ? true : false; + final boolean success = event == Event.RebalanceCompleted; final AgentAttache attache = findAttache(hostId); logger.debug("Finishing rebalancing for the agent {} ({}) with event {}", hostId, attache, event); - if (attache == null || !(attache instanceof ClusteredAgentAttache)) { + if (!(attache instanceof ClusteredAgentAttache)) { logger.debug("Unable to find forward attache for the host id={} assuming that the agent disconnected already", hostId); _hostTransferDao.completeAgentTransfer(hostId); return; @@ -1174,9 +1196,9 @@ protected void cleanupTransferMap(final long msId) { } protected class RebalanceTask extends ManagedContextRunnable { - Long hostId = null; - Long currentOwnerId = null; - Long futureOwnerId = null; + Long hostId; + Long currentOwnerId; + Long futureOwnerId; public RebalanceTask(final long hostId, final long currentOwnerId, final long futureOwnerId) { this.hostId = hostId; @@ -1245,7 +1267,7 @@ public String dispatch(final ClusterServicePdu pdu) { final ChangeAgentCommand cmd = (ChangeAgentCommand) cmds[0]; logger.debug("Intercepting command for agent change: agent {} event: {}", cmd.getAgentId(), cmd.getEvent()); - boolean result = false; + boolean result; try { result = executeAgentUserRequest(cmd.getAgentId(), cmd.getEvent()); logger.debug("Result is {}", result); @@ -1261,10 +1283,10 @@ public String dispatch(final ClusterServicePdu pdu) { } else if (cmds.length == 1 && cmds[0] instanceof TransferAgentCommand) { final TransferAgentCommand cmd = (TransferAgentCommand) cmds[0]; - logger.debug("Intercepting command for agent rebalancing: agent {} event: {}", cmd.getAgentId(), cmd.getEvent()); - boolean result = false; + logger.debug("Intercepting command for agent rebalancing: agent: {}, event: {}, connection transfer: {}", cmd.getAgentId(), cmd.getEvent(), cmd.isConnectionTransfer()); + boolean result; try { - result = rebalanceAgent(cmd.getAgentId(), cmd.getEvent(), cmd.getCurrentOwner(), cmd.getFutureOwner()); + result = rebalanceAgent(cmd.getAgentId(), cmd.getEvent(), cmd.getCurrentOwner(), cmd.getFutureOwner(), cmd.isConnectionTransfer()); logger.debug("Result is {}", result); } catch (final AgentUnavailableException e) { @@ -1282,13 +1304,22 @@ public String dispatch(final ClusterServicePdu pdu) { logger.debug("Intercepting command to propagate event {} for host {} ({})", () -> cmd.getEvent().name(), cmd::getHostId, () -> _hostDao.findById(cmd.getHostId())); - boolean result = false; + boolean result; try { - result = _resourceMgr.executeUserRequest(cmd.getHostId(), cmd.getEvent()); + result = _resourceMgr.executeUserRequest(cmd.getHostId(), cmd.getEvent(), cmd.isForced(), cmd.isForceDeleteStorage()); logger.debug("Result is {}", result); } catch (final AgentUnavailableException ex) { logger.warn("Agent is unavailable", ex); return null; + } catch (final RuntimeException ex) { + logger.error(String.format("Failed to execute propagated event %s for host %d", cmd.getEvent().name(), cmd.getHostId()), ex); + final Answer[] answers = new Answer[1]; + String details = ex.getMessage(); + if (details == null || details.isEmpty()) { + details = ex.toString(); + } + answers[0] = new Answer(cmd, false, details); + return _gson.toJson(answers); } final Answer[] answers = new Answer[1]; @@ -1300,6 +1331,8 @@ public String dispatch(final ClusterServicePdu pdu) { } else if (cmds.length == 1 && cmds[0] instanceof BaseShutdownManagementServerHostCommand) { final BaseShutdownManagementServerHostCommand cmd = (BaseShutdownManagementServerHostCommand) cmds[0]; return handleShutdownManagementServerHostCommand(cmd); + } else if (cmds.length == 1 && cmds[0] instanceof ExtensionServerActionBaseCommand) { + return extensionsManager.handleExtensionServerCommands((ExtensionServerActionBaseCommand)cmds[0]); } try { @@ -1328,10 +1361,28 @@ public String dispatch(final ClusterServicePdu pdu) { } private String handleShutdownManagementServerHostCommand(BaseShutdownManagementServerHostCommand cmd) { + if (cmd instanceof PrepareForMaintenanceManagementServerHostCommand) { + logger.debug("Received PrepareForMaintenanceManagementServerHostCommand - preparing for maintenance"); + try { + managementServerMaintenanceManager.prepareForMaintenance(((PrepareForMaintenanceManagementServerHostCommand) cmd).getLbAlgorithm(), ((PrepareForMaintenanceManagementServerHostCommand) cmd).isForced()); + return "Successfully prepared for maintenance"; + } catch(CloudRuntimeException e) { + return e.getMessage(); + } + } + if (cmd instanceof CancelMaintenanceManagementServerHostCommand) { + logger.debug("Received CancelMaintenanceManagementServerHostCommand - cancelling maintenance"); + try { + managementServerMaintenanceManager.cancelMaintenance(); + return "Successfully cancelled maintenance"; + } catch(CloudRuntimeException e) { + return e.getMessage(); + } + } if (cmd instanceof PrepareForShutdownManagementServerHostCommand) { - logger.debug("Received BaseShutdownManagementServerHostCommand - preparing to shut down"); + logger.debug("Received PrepareForShutdownManagementServerHostCommand - preparing to shut down"); try { - shutdownManager.prepareForShutdown(); + managementServerMaintenanceManager.prepareForShutdown(); return "Successfully prepared for shutdown"; } catch (CloudRuntimeException e) { return e.getMessage(); @@ -1340,7 +1391,7 @@ private String handleShutdownManagementServerHostCommand(BaseShutdownManagementS if (cmd instanceof TriggerShutdownManagementServerHostCommand) { logger.debug("Received TriggerShutdownManagementServerHostCommand - triggering a shut down"); try { - shutdownManager.triggerShutdown(); + managementServerMaintenanceManager.triggerShutdown(); return "Successfully triggered shutdown"; } catch (CloudRuntimeException e) { return e.getMessage(); @@ -1349,8 +1400,8 @@ private String handleShutdownManagementServerHostCommand(BaseShutdownManagementS if (cmd instanceof CancelShutdownManagementServerHostCommand) { logger.debug("Received CancelShutdownManagementServerHostCommand - cancelling shut down"); try { - shutdownManager.cancelShutdown(); - return "Successfully prepared for shutdown"; + managementServerMaintenanceManager.cancelShutdown(); + return "Successfully cancelled shutdown"; } catch (CloudRuntimeException e) { return e.getMessage(); } @@ -1359,6 +1410,146 @@ private String handleShutdownManagementServerHostCommand(BaseShutdownManagementS } } + @Override + public boolean transferDirectAgentsFromMS(String fromMsUuid, long fromMsId, long timeoutDurationInMs, boolean excludeHostsInMaintenance) { + if (timeoutDurationInMs <= 0) { + logger.debug("Not transferring direct agents from management server node {} (id: {}) to other nodes, invalid timeout duration", fromMsId, fromMsUuid); + return false; + } + + long transferStartTimeInMs = System.currentTimeMillis(); + if (CollectionUtils.isEmpty(getDirectAgentHosts(fromMsId, excludeHostsInMaintenance))) { + logger.info("No direct agent hosts available on management server node {} (id: {}), to transfer", fromMsId, fromMsUuid); + return true; + } + + List msHosts = getUpMsHostsExcludingMs(fromMsId); + if (msHosts.isEmpty()) { + logger.warn("No management server nodes available to transfer agents from management server node {} (id: {})", fromMsId, fromMsUuid); + return false; + } + + logger.debug("Transferring direct agents from management server node {} (id: {}) to other nodes", fromMsId, fromMsUuid); + int agentTransferFailedCount = 0; + List dataCenterList = dcDao.listAll(); + for (DataCenterVO dc : dataCenterList) { + List directAgentHostsInDc = getDirectAgentHostsInDc(fromMsId, dc.getId(), excludeHostsInMaintenance); + if (CollectionUtils.isEmpty(directAgentHostsInDc)) { + continue; + } + logger.debug("Transferring {} direct agents from management server node {} (id: {}) of zone {}", directAgentHostsInDc.size(), fromMsId, fromMsUuid, dc); + for (HostVO host : directAgentHostsInDc) { + long transferElapsedTimeInMs = System.currentTimeMillis() - transferStartTimeInMs; + if (transferElapsedTimeInMs >= timeoutDurationInMs) { + logger.debug("Stop transferring remaining direct agents from management server node {} (id: {}), timed out", fromMsId, fromMsUuid); + return false; + } + + try { + if (_mshostCounter >= msHosts.size()) { + _mshostCounter = 0; + } + ManagementServerHostVO msHost = msHosts.get(_mshostCounter % msHosts.size()); + _mshostCounter++; + + _hostTransferDao.startAgentTransfering(host.getId(), fromMsId, msHost.getMsid()); + if (!rebalanceAgent(host.getId(), Event.StartAgentRebalance, fromMsId, msHost.getMsid(), true)) { + agentTransferFailedCount++; + } else { + updateLastManagementServer(host.getId(), fromMsId); + } + } catch (Exception e) { + logger.warn("Failed to transfer direct agent of the host {} from management server node {} (id: {}), due to {}", host, fromMsId, fromMsUuid, e.getMessage()); + } + } + } + + return (agentTransferFailedCount == 0); + } + + private List getDirectAgentHosts(long msId, boolean excludeHostsInMaintenance) { + List directAgentHosts = new ArrayList<>(); + List statesToExclude = excludeHostsInMaintenance ? ResourceState.s_maintenanceStates : List.of(); + List hosts = _hostDao.listHostsByMsResourceState(msId, statesToExclude); + for (HostVO host : hosts) { + AgentAttache agent = findAttache(host.getId()); + if (agent instanceof DirectAgentAttache) { + directAgentHosts.add(host); + } + } + + return directAgentHosts; + } + + private List getDirectAgentHostsInDc(long msId, long dcId, boolean excludeHostsInMaintenance) { + List directAgentHosts = new ArrayList<>(); + // To exclude maintenance states use values from ResourceState as source of truth + List statesToExclude = excludeHostsInMaintenance ? ResourceState.s_maintenanceStates : List.of(); + List hosts = _hostDao.listHostsByMsDcResourceState(msId, dcId, statesToExclude); + for (HostVO host : hosts) { + AgentAttache agent = findAttache(host.getId()); + if (agent instanceof DirectAgentAttache) { + directAgentHosts.add(host); + } + } + + return directAgentHosts; + } + + private List getUpMsHostsExcludingMs(long avoidMsId) { + final List msHosts = _mshostDao.listBy(ManagementServerHost.State.Up); + msHosts.removeIf(ms -> ms.getMsid() == avoidMsId || _mshostPeerDao.findByPeerMsAndState(ms.getId(), ManagementServerHost.State.Up) == null); + + return msHosts; + } + + private void updateLastManagementServer(long hostId, long msId) { + HostVO hostVO = _hostDao.findById(hostId); + if (hostVO != null) { + hostVO.setLastManagementServerId(msId); + _hostDao.update(hostId, hostVO); + } + } + + @Override + public void onManagementServerPreparingForMaintenance() { + logger.debug("Management server preparing for maintenance"); + super.onManagementServerPreparingForMaintenance(); + } + + @Override + public void onManagementServerCancelPreparingForMaintenance() { + logger.debug("Management server cancel preparing for maintenance"); + super.onManagementServerPreparingForMaintenance(); + + // needed for the case when Management Server in Preparing For Maintenance but didn't go to Maintenance state + // (where this variable will be reset) + _agentLbHappened = false; + } + + @Override + public void onManagementServerMaintenance() { + logger.debug("Management server maintenance enabled"); + s_transferExecutor.shutdownNow(); + cleanupTransferMap(_nodeId); + _agentLbHappened = false; + super.onManagementServerMaintenance(); + } + + @Override + public void onManagementServerCancelMaintenance() { + logger.debug("Management server maintenance disabled"); + super.onManagementServerCancelMaintenance(); + if (isAgentRebalanceEnabled()) { + cleanupTransferMap(_nodeId); + if (s_transferExecutor.isShutdown()) { + s_transferExecutor = Executors.newScheduledThreadPool(2, new NamedThreadFactory("Cluster-AgentRebalancingExecutor")); + s_transferExecutor.scheduleAtFixedRate(getAgentRebalanceScanTask(), 60000, 60000, TimeUnit.MILLISECONDS); + s_transferExecutor.scheduleAtFixedRate(getTransferScanTask(), 60000, ClusteredAgentRebalanceService.DEFAULT_TRANSFER_CHECK_INTERVAL, TimeUnit.MILLISECONDS); + } + } + } + public boolean executeAgentUserRequest(final long agentId, final Event event) throws AgentUnavailableException { return executeUserRequest(agentId, event); } @@ -1367,6 +1558,10 @@ public boolean rebalanceAgent(final long agentId, final Event event, final long return executeRebalanceRequest(agentId, currentOwnerId, futureOwnerId, event); } + public boolean rebalanceAgent(final long agentId, final Event event, final long currentOwnerId, final long futureOwnerId, boolean isConnectionTransfer) throws AgentUnavailableException, OperationTimedoutException { + return executeRebalanceRequest(agentId, currentOwnerId, futureOwnerId, event, isConnectionTransfer); + } + public boolean isAgentRebalanceEnabled() { return EnableLB.value(); } @@ -1421,8 +1616,7 @@ public void rescan() { public ConfigKey[] getConfigKeys() { final ConfigKey[] keys = super.getConfigKeys(); - final List> keysLst = new ArrayList>(); - keysLst.addAll(Arrays.asList(keys)); + final List> keysLst = new ArrayList<>(Arrays.asList(keys)); keysLst.add(EnableLB); keysLst.add(ConnectedAgentThreshold); keysLst.add(LoadSize); diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredDirectAgentAttache.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredDirectAgentAttache.java index e36ea6cedc13..5e796d8e0d86 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredDirectAgentAttache.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ClusteredDirectAgentAttache.java @@ -20,14 +20,15 @@ import com.cloud.agent.transport.Response; import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.UnsupportedVersionException; +import com.cloud.hypervisor.Hypervisor; import com.cloud.resource.ServerResource; import com.cloud.utils.exception.CloudRuntimeException; public class ClusteredDirectAgentAttache extends DirectAgentAttache implements Routable { private final long _nodeId; - public ClusteredDirectAgentAttache(ClusteredAgentManagerImpl agentMgr, long id, String uuid, String name, long mgmtId, ServerResource resource, boolean maintenance) { - super(agentMgr, id, uuid, name, resource, maintenance); + public ClusteredDirectAgentAttache(ClusteredAgentManagerImpl agentMgr, long id, String uuid, String name, final Hypervisor.HypervisorType hypervisorType, long mgmtId, ServerResource resource, boolean maintenance) { + super(agentMgr, id, uuid, name, hypervisorType, resource, maintenance); _nodeId = mgmtId; } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/ConnectedAgentAttache.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/ConnectedAgentAttache.java index 523f98fd0108..f4efaaa34a42 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/ConnectedAgentAttache.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/ConnectedAgentAttache.java @@ -22,6 +22,7 @@ import com.cloud.agent.transport.Request; import com.cloud.exception.AgentUnavailableException; import com.cloud.host.Status; +import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.nio.Link; /** @@ -31,8 +32,8 @@ public class ConnectedAgentAttache extends AgentAttache { protected Link _link; - public ConnectedAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Link link, final boolean maintenance) { - super(agentMgr, id, uuid, name, maintenance); + public ConnectedAgentAttache(final AgentManagerImpl agentMgr, final long id, final String uuid, final String name, final Hypervisor.HypervisorType hypervisorType, final Link link, final boolean maintenance) { + super(agentMgr, id, uuid, name, hypervisorType, maintenance); _link = link; } @@ -53,8 +54,10 @@ public synchronized boolean isClosed() { @Override public void disconnect(final Status state) { synchronized (this) { - logger.debug("Processing Disconnect."); + logger.debug("Processing disconnect [id: {}, uuid: {}, name: {}]", _id, _uuid, _name); + if (_link != null) { + logger.debug("Disconnecting from {}, Socket Address: {}", _link.getIpAddress(), _link.getSocketAddress()); _link.close(); _link.terminated(); } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/DirectAgentAttache.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/DirectAgentAttache.java index 07d5bf803932..ed18d1e82b7e 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/DirectAgentAttache.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/DirectAgentAttache.java @@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +import com.cloud.utils.ThreadUtil; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.managed.context.ManagedContextRunnable; @@ -35,6 +36,7 @@ import com.cloud.agent.transport.Response; import com.cloud.exception.AgentUnavailableException; import com.cloud.host.Status; +import com.cloud.hypervisor.Hypervisor; import com.cloud.resource.ServerResource; import org.apache.logging.log4j.ThreadContext; @@ -51,8 +53,8 @@ public class DirectAgentAttache extends AgentAttache { AtomicInteger _outstandingTaskCount; AtomicInteger _outstandingCronTaskCount; - public DirectAgentAttache(AgentManagerImpl agentMgr, long id, String uuid,String name, ServerResource resource, boolean maintenance) { - super(agentMgr, id, uuid, name, maintenance); + public DirectAgentAttache(AgentManagerImpl agentMgr, long id, String uuid, String name, final Hypervisor.HypervisorType hypervisorType, ServerResource resource, boolean maintenance) { + super(agentMgr, id, uuid, name, hypervisorType, maintenance); _resource = resource; _outstandingTaskCount = new AtomicInteger(0); _outstandingCronTaskCount = new AtomicInteger(0); @@ -165,7 +167,7 @@ protected synchronized void runInContext() { PingCommand cmd = resource.getCurrentStatus(_id); int retried = 0; while (cmd == null && ++retried <= _HostPingRetryCount.value()) { - Thread.sleep(1000*_HostPingRetryTimer.value()); + ThreadUtil.wait(this, 1000L *_HostPingRetryTimer.value(), _id, _uuid, _name); cmd = resource.getCurrentStatus(_id); } diff --git a/engine/orchestration/src/main/java/com/cloud/agent/manager/DummyAttache.java b/engine/orchestration/src/main/java/com/cloud/agent/manager/DummyAttache.java index 2f15e7af43c3..f2b253fffcbd 100644 --- a/engine/orchestration/src/main/java/com/cloud/agent/manager/DummyAttache.java +++ b/engine/orchestration/src/main/java/com/cloud/agent/manager/DummyAttache.java @@ -19,11 +19,12 @@ import com.cloud.agent.transport.Request; import com.cloud.exception.AgentUnavailableException; import com.cloud.host.Status; +import com.cloud.hypervisor.Hypervisor; public class DummyAttache extends AgentAttache { - public DummyAttache(AgentManagerImpl agentMgr, long id, String uuid, String name, boolean maintenance) { - super(agentMgr, id, uuid, name, maintenance); + public DummyAttache(AgentManagerImpl agentMgr, long id, String uuid, String name, final Hypervisor.HypervisorType hypervisorType, boolean maintenance) { + super(agentMgr, id, uuid, name, hypervisorType, maintenance); } @Override diff --git a/engine/orchestration/src/main/java/com/cloud/cluster/ClusteredAgentRebalanceService.java b/engine/orchestration/src/main/java/com/cloud/cluster/ClusteredAgentRebalanceService.java index ed52eb1a241b..524b1c3adb6c 100644 --- a/engine/orchestration/src/main/java/com/cloud/cluster/ClusteredAgentRebalanceService.java +++ b/engine/orchestration/src/main/java/com/cloud/cluster/ClusteredAgentRebalanceService.java @@ -27,4 +27,5 @@ public interface ClusteredAgentRebalanceService { boolean executeRebalanceRequest(long agentId, long currentOwnerId, long futureOwnerId, Event event) throws AgentUnavailableException, OperationTimedoutException; + boolean executeRebalanceRequest(long agentId, long currentOwnerId, long futureOwnerId, Event event, boolean isConnectionTransfer) throws AgentUnavailableException, OperationTimedoutException; } 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 74ff3801b6a3..364db685c9de 100755 --- a/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java +++ b/engine/orchestration/src/main/java/com/cloud/vm/VirtualMachineManagerImpl.java @@ -17,6 +17,7 @@ package com.cloud.vm; +import static com.cloud.configuration.ConfigurationManagerImpl.EXPOSE_ERRORS_TO_USER; import static com.cloud.configuration.ConfigurationManagerImpl.MIGRATE_VM_ACROSS_CLUSTERS; import java.lang.reflect.Field; @@ -49,6 +50,9 @@ import javax.naming.ConfigurationException; import javax.persistence.EntityExistsException; +import com.cloud.agent.api.PostMigrationCommand; +import com.cloud.storage.clvm.ClvmPoolManager; +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; @@ -57,6 +61,8 @@ import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.backup.BackupManager; +import org.apache.cloudstack.backup.dao.BackupDao; import org.apache.cloudstack.ca.CAManager; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; @@ -67,9 +73,14 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; 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.extensions.dao.ExtensionDetailsDao; +import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; +import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO; import org.apache.cloudstack.framework.jobs.AsyncJob; import org.apache.cloudstack.framework.jobs.AsyncJobExecutionContext; import org.apache.cloudstack.framework.jobs.AsyncJobManager; @@ -82,11 +93,13 @@ import org.apache.cloudstack.framework.messagebus.MessageBus; import org.apache.cloudstack.framework.messagebus.MessageDispatcher; import org.apache.cloudstack.framework.messagebus.MessageHandler; +import org.apache.cloudstack.gpu.GpuService; import org.apache.cloudstack.jobs.JobInfo; import org.apache.cloudstack.managed.context.ManagedContextRunnable; import org.apache.cloudstack.reservation.dao.ReservationDao; import org.apache.cloudstack.resource.ResourceCleanupService; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.cloudstack.utils.cache.SingleCache; @@ -96,6 +109,7 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.collections.MapUtils; import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang3.ObjectUtils; import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; @@ -120,10 +134,14 @@ import com.cloud.agent.api.PingRoutingCommand; import com.cloud.agent.api.PlugNicAnswer; import com.cloud.agent.api.PlugNicCommand; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.PrepareExternalProvisioningCommand; import com.cloud.agent.api.PrepareForMigrationAnswer; import com.cloud.agent.api.PrepareForMigrationCommand; +import com.cloud.agent.api.PreMigrationCommand; import com.cloud.agent.api.RebootAnswer; import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.RecreateCheckpointsCommand; import com.cloud.agent.api.ReplugNicAnswer; import com.cloud.agent.api.ReplugNicCommand; import com.cloud.agent.api.RestoreVMSnapshotAnswer; @@ -137,11 +155,15 @@ import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.UnPlugNicAnswer; import com.cloud.agent.api.UnPlugNicCommand; +import com.cloud.agent.api.UnmanageInstanceCommand; import com.cloud.agent.api.UnregisterVMCommand; +import com.cloud.agent.api.UpdateVmNicAnswer; +import com.cloud.agent.api.UpdateVmNicCommand; import com.cloud.agent.api.VmDiskStatsEntry; import com.cloud.agent.api.VmNetworkStatsEntry; import com.cloud.agent.api.VmStatsEntry; import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.DataTO; import com.cloud.agent.api.to.DiskTO; import com.cloud.agent.api.to.DpdkTO; import com.cloud.agent.api.to.GPUDeviceTO; @@ -199,12 +221,14 @@ import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; +import com.cloud.host.dao.HostDetailsDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuru; import com.cloud.hypervisor.HypervisorGuruBase; import com.cloud.hypervisor.HypervisorGuruManager; import com.cloud.network.Network; import com.cloud.network.NetworkModel; +import com.cloud.network.NetworkService; import com.cloud.network.Networks; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkDetailVO; @@ -227,6 +251,7 @@ import com.cloud.service.dao.ServiceOfferingDao; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.ScopeType; +import com.cloud.storage.Snapshot; import com.cloud.storage.Storage; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageManager; @@ -245,6 +270,8 @@ import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.dao.VolumeDetailsDao; +import com.cloud.storage.snapshot.SnapshotManager; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; @@ -280,8 +307,8 @@ import com.cloud.vm.VirtualMachine.State; import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.UserVmDetailsDao; import com.cloud.vm.dao.VMInstanceDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.snapshot.VMSnapshotManager; import com.cloud.vm.snapshot.VMSnapshotVO; import com.cloud.vm.snapshot.dao.VMSnapshotDao; @@ -328,6 +355,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private HostDao _hostDao; @Inject + private HostDetailsDao hostDetailsDao; + @Inject private AlertManager _alertMgr; @Inject private GuestOSCategoryDao _guestOsCategoryDao; @@ -336,6 +365,8 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private VolumeDao _volsDao; @Inject + private VolumeDetailsDao _volsDetailsDao; + @Inject private HighAvailabilityManager _haMgr; @Inject private HostPodDao _podDao; @@ -374,10 +405,12 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private ClusterDetailsDao _clusterDetailsDao; @Inject - private UserVmDetailsDao userVmDetailsDao; + private VMInstanceDetailsDao vmInstanceDetailsDao; @Inject private VolumeOrchestrationService volumeMgr; @Inject + private GpuService gpuService; + @Inject private DeploymentPlanningManager _dpMgr; @Inject private MessageBus _messageBus; @@ -410,14 +443,36 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac @Inject private DomainDao domainDao; @Inject + public NetworkService networkService; + @Inject ResourceCleanupService resourceCleanupService; @Inject VmWorkJobDao vmWorkJobDao; @Inject DataStoreProviderManager dataStoreProviderManager; + @Inject + BackupManager backupManager; + @Inject + BackupDao backupDao; private SingleCache> vmIdsInProgressCache; + @Inject + private SnapshotDataStoreDao snapshotDataStoreDao; + + @Inject + private SnapshotManager snapshotManager; + + @Inject + private VolumeDataFactory volumeDataFactory; + @Inject + ExtensionsManager extensionsManager; + @Inject + ExtensionDetailsDao extensionDetailsDao; + @Inject + ClvmPoolManager clvmPoolManager; + + VmWorkJobHandlerProxy _jobHandlerProxy = new VmWorkJobHandlerProxy(this); Map _vmGurus = new HashMap<>(); @@ -444,7 +499,7 @@ public class VirtualMachineManagerImpl extends ManagerBase implements VirtualMac static final ConfigKey ClusterVMMetaDataSyncInterval = new ConfigKey("Advanced", Integer.class, "vmmetadata.sync.interval", "180", "Cluster VM metadata sync interval in seconds", false); - static final ConfigKey VmJobCheckInterval = new ConfigKey("Advanced", + public static final ConfigKey VmJobCheckInterval = new ConfigKey("Advanced", Long.class, "vm.job.check.interval", "3000", "Interval in milliseconds to check if the job is complete", false); static final ConfigKey VmJobTimeout = new ConfigKey("Advanced", @@ -489,8 +544,9 @@ public void registerGuru(final VirtualMachine.Type type, final VirtualMachineGur @Override @DB public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, - final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, - final LinkedHashMap> auxiliaryNetworks, final DeploymentPlan plan, final HypervisorType hyperType, final Map> extraDhcpOptions, final Map datadiskTemplateToDiskOfferingMap) + final DiskOfferingInfo rootDiskOfferingInfo, final List dataDiskOfferings, List dataDiskDeviceIds, + final LinkedHashMap> auxiliaryNetworks,final DeploymentPlan plan, final HypervisorType hyperType, + final Map> extraDhcpOptions, final Map datadiskTemplateToDiskOfferingMap, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { logger.info("Allocating Instance from Template: {} with hostname: {} and {} networks", template, vmInstanceName, auxiliaryNetworks.size()); @@ -528,25 +584,34 @@ public void allocate(final String vmInstanceName, final VirtualMachineTemplate t logger.debug("Allocating disks for {}", persistedVm); - allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal); + if (isBlankInstance(template)) { + logger.debug("Template is a dummy template for hypervisor {}, skipping volume allocation", hyperType); + return; + } else { + allocateRootVolume(persistedVm, template, rootDiskOfferingInfo, owner, rootDiskSizeFinal, volume, snapshot); + } + // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); try { if (dataDiskOfferings != null) { + int index = 0; for (final DiskOfferingInfo dataDiskOfferingInfo : dataDiskOfferings) { - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId(), dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), - dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, null, true); + Long deviceId = dataDiskDeviceIds.get(index++); + String volumeName = deviceId == null ? "DATA-" + persistedVm.getId() : "DATA-" + persistedVm.getId() + "-" + String.valueOf(deviceId); + volumeMgr.allocateRawVolume(Type.DATADISK, volumeName, dataDiskOfferingInfo.getDiskOffering(), dataDiskOfferingInfo.getSize(), + dataDiskOfferingInfo.getMinIops(), dataDiskOfferingInfo.getMaxIops(), persistedVm, template, owner, deviceId, dataDiskOfferingInfo.getKmsKeyId(), true); } } if (datadiskTemplateToDiskOfferingMap != null && !datadiskTemplateToDiskOfferingMap.isEmpty()) { - int diskNumber = 1; + Long diskNumber = 1L; for (Entry dataDiskTemplateToDiskOfferingMap : datadiskTemplateToDiskOfferingMap.entrySet()) { DiskOffering diskOffering = dataDiskTemplateToDiskOfferingMap.getValue(); long diskOfferingSize = diskOffering.getDiskSize() / (1024 * 1024 * 1024); VMTemplateVO dataDiskTemplate = _templateDao.findById(dataDiskTemplateToDiskOfferingMap.getKey()); - volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf(diskNumber), diskOffering, diskOfferingSize, null, null, - persistedVm, dataDiskTemplate, owner, Long.valueOf(diskNumber), true); + volumeMgr.allocateRawVolume(Type.DATADISK, "DATA-" + persistedVm.getId() + "-" + String.valueOf( diskNumber), diskOffering, diskOfferingSize, null, null, + persistedVm, dataDiskTemplate, owner, diskNumber, null, true); diskNumber++; } } @@ -569,19 +634,19 @@ public void allocate(final String vmInstanceName, final VirtualMachineTemplate t } } - private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, Account owner, Long rootDiskSizeFinal) { + private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template, DiskOfferingInfo rootDiskOfferingInfo, Account owner, Long rootDiskSizeFinal, Volume volume, Snapshot snapshot) { // Create new Volume context and inject event resource type, id and details to generate VOLUME.CREATE event for the ROOT disk. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); try { String rootVolumeName = String.format("ROOT-%s", vm.getId()); if (template.getFormat() == ImageFormat.ISO) { volumeMgr.allocateRawVolume(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskOfferingInfo.getSize(), - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, true); - } else if (template.getFormat() == ImageFormat.BAREMETAL) { - logger.debug("%s has format [{}]. Skipping ROOT volume [{}] allocation.", template.toString(), ImageFormat.BAREMETAL, rootVolumeName); + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), vm, template, owner, null, rootDiskOfferingInfo.getKmsKeyId(), true); + } else if (Arrays.asList(ImageFormat.BAREMETAL, ImageFormat.EXTERNAL).contains(template.getFormat())) { + logger.debug("{} has format [{}]. Skipping ROOT volume [{}] allocation.", template, template.getFormat(), rootVolumeName); } else { volumeMgr.allocateTemplatedVolumes(Type.ROOT, rootVolumeName, rootDiskOfferingInfo.getDiskOffering(), rootDiskSizeFinal, - rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner); + rootDiskOfferingInfo.getMinIops(), rootDiskOfferingInfo.getMaxIops(), template, vm, owner, rootDiskOfferingInfo.getKmsKeyId(), volume, snapshot); } } finally { // Remove volumeContext and pop vmContext back @@ -591,9 +656,9 @@ private void allocateRootVolume(VMInstanceVO vm, VirtualMachineTemplate template @Override public void allocate(final String vmInstanceName, final VirtualMachineTemplate template, final ServiceOffering serviceOffering, - final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType) throws InsufficientCapacityException { + final LinkedHashMap> networks, final DeploymentPlan plan, final HypervisorType hyperType, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { DiskOffering diskOffering = _diskOfferingDao.findById(serviceOffering.getDiskOfferingId()); - allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), networks, plan, hyperType, null, null); + allocate(vmInstanceName, template, serviceOffering, new DiskOfferingInfo(diskOffering), new ArrayList<>(), new ArrayList<>(), networks, plan, hyperType, null, null, volume, snapshot); } VirtualMachineGuru getVmGuru(final VirtualMachine vm) { @@ -638,6 +703,13 @@ protected void advanceExpunge(VMInstanceVO vm) throws ResourceUnavailableExcepti return; } + if (HypervisorType.External.equals(vm.getHypervisorType())) { + UserVmVO userVM = _userVmDao.findById(vm.getId()); + _userVmDao.loadDetails(userVM); + userVM.setDetail(VmDetailConstants.EXPUNGE_EXTERNAL_VM, Boolean.TRUE.toString()); + _userVmDao.saveDetails(userVM); + } + advanceStop(vm.getUuid(), VmDestroyForcestop.value()); vm = _vmDao.findByUuid(vm.getUuid()); @@ -875,11 +947,27 @@ public void start(final String vmUuid, final Map params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) { try { advanceStart(vmUuid, params, planToDeploy, planner); - } catch (ConcurrentOperationException | InsufficientCapacityException e) { - throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid); + } catch (ConcurrentOperationException e) { + final CallContext cctxt = CallContext.current(); + final Account account = cctxt.getCallingAccount(); + if (canExposeError(account)) { + throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid); + } + throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to concurrent operation.", vmUuid), e).add(VirtualMachine.class, vmUuid); + } catch (final InsufficientCapacityException e) { + final CallContext cctxt = CallContext.current(); + final Account account = cctxt.getCallingAccount(); + if (canExposeError(account)) { + throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid); + } + throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to insufficient capacity.", vmUuid), e).add(VirtualMachine.class, vmUuid); } catch (final ResourceUnavailableException e) { - if (e.getScope() != null && e.getScope().equals(VirtualRouter.class)){ - throw new CloudRuntimeException("Network is unavailable. Please contact administrator", e).add(VirtualMachine.class, vmUuid); + if (e.getScope() != null && e.getScope().equals(VirtualRouter.class)) { + Account callingAccount = CallContext.current().getCallingAccount(); + String errorSuffix = (callingAccount != null && callingAccount.getType() == Account.Type.ADMIN) ? + String.format("Failure: %s", e.getMessage()) : + "Please contact administrator."; + throw new CloudRuntimeException(String.format("The Network for VM %s is unavailable. %s", vmUuid, errorSuffix), e).add(VirtualMachine.class, vmUuid); } throw new CloudRuntimeException(String.format("Unable to start a VM [%s] due to [%s].", vmUuid, e.getMessage()), e).add(VirtualMachine.class, vmUuid); } @@ -941,7 +1029,7 @@ public Ternary doInTransaction(final if (stateTransitTo(vm, Event.StartRequested, null, work.getId())) { logger.debug("Successfully transitioned to start state for {} reservation id = {}", vm, work.getId()); if (VirtualMachine.Type.User.equals(vm.type) && ResourceCountRunningVMsonly.value()) { - _resourceLimitMgr.incrementVmResourceCount(owner.getAccountId(), vm.isDisplay(), offering, template); + _resourceLimitMgr.incrementVmResourceCount(owner.getAccountId(), vm.isDisplay(), offering, template, null); } return new Ternary<>(vm, context, work); } @@ -1130,6 +1218,141 @@ protected void updateVmMetadataManufacturerAndProduct(VirtualMachineTO vmTO, VMI vmTO.setMetadataProductName(metadataProduct); } + protected void updateExternalVmDetailsFromPrepareAnswer(VirtualMachineTO vmTO, UserVmVO userVmVO, + Map newDetails) { + if (newDetails == null || newDetails.equals(vmTO.getDetails())) { + return; + } + vmTO.setDetails(newDetails); + userVmVO.setDetails(newDetails); + _userVmDao.saveDetails(userVmVO); + } + + protected void updateExternalVmDataFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { + final String vncPassword = updatedTO.getVncPassword(); + final Map details = updatedTO.getDetails(); + if ((vncPassword == null || vncPassword.equals(vmTO.getVncPassword())) && + (details == null || details.equals(vmTO.getDetails()))) { + return; + } + UserVmVO userVmVO = _userVmDao.findById(vmTO.getId()); + if (userVmVO == null) { + return; + } + if (vncPassword != null && !vncPassword.equals(userVmVO.getPassword())) { + userVmVO.setVncPassword(vncPassword); + vmTO.setVncPassword(vncPassword); + } + updateExternalVmDetailsFromPrepareAnswer(vmTO, userVmVO, updatedTO.getDetails()); + } + + protected void updateExternalVmNicsFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { + if (ObjectUtils.anyNull(vmTO.getNics(), updatedTO.getNics())) { + return; + } + Map originalNicsByUuid = new HashMap<>(); + for (NicTO nic : vmTO.getNics()) { + originalNicsByUuid.put(nic.getNicUuid(), nic); + } + for (NicTO updatedNicTO : updatedTO.getNics()) { + final String nicUuid = updatedNicTO.getNicUuid(); + NicTO originalNicTO = originalNicsByUuid.get(nicUuid); + if (originalNicTO == null) { + continue; + } + final String mac = updatedNicTO.getMac(); + final String ip4 = updatedNicTO.getIp(); + final String ip6 = updatedNicTO.getIp6Address(); + if (Objects.equals(mac, originalNicTO.getMac()) && + Objects.equals(ip4, originalNicTO.getIp()) && + Objects.equals(ip6, originalNicTO.getIp6Address())) { + continue; + } + NicVO nicVO = _nicsDao.findByUuid(nicUuid); + if (nicVO == null) { + continue; + } + logger.debug("Updating {} during External VM preparation", nicVO); + if (ip4 != null && !ip4.equals(nicVO.getIPv4Address())) { + nicVO.setIPv4Address(ip4); + originalNicTO.setIp(ip4); + } + if (ip6 != null && !ip6.equals(nicVO.getIPv6Address())) { + nicVO.setIPv6Address(ip6); + originalNicTO.setIp6Address(ip6); + } + if (mac != null && !mac.equals(nicVO.getMacAddress())) { + nicVO.setMacAddress(mac); + originalNicTO.setMac(mac); + } + _nicsDao.update(nicVO.getId(), nicVO); + } + } + + protected void updateExternalVmFromPrepareAnswer(VirtualMachineTO vmTO, VirtualMachineTO updatedTO) { + if (updatedTO == null) { + return; + } + updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); + updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + return; + } + + protected void processPrepareExternalProvisioning(boolean firstStart, Host host, + VirtualMachineProfile vmProfile, DataCenter dataCenter) throws CloudRuntimeException { + VirtualMachineTemplate template = vmProfile.getTemplate(); + if (!firstStart || host == null || !HypervisorType.External.equals(host.getHypervisorType()) || + template.getExtensionId() == null) { + return; + } + ExtensionDetailsVO detailsVO = extensionDetailsDao.findDetail(template.getExtensionId(), + ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM); + if (detailsVO == null || !Boolean.parseBoolean(detailsVO.getValue())) { + return; + } + logger.debug("Sending PrepareExternalProvisioningCommand for {}", vmProfile); + VirtualMachineTO virtualMachineTO = toVmTO(vmProfile); + if (virtualMachineTO.getNics() == null || virtualMachineTO.getNics().length == 0) { + List nics = _nicsDao.listByVmId(vmProfile.getId()); + NicTO[] nicTOs = new NicTO[nics.size()]; + nics.forEach(nicVO -> { + NicTO nicTO = toNicTO(_networkModel.getNicProfile(vmProfile.getVirtualMachine(), nicVO, dataCenter), + HypervisorType.External); + nicTOs[nicTO.getDeviceId()] = nicTO; + }); + virtualMachineTO.setNics(nicTOs); + } + Map vmDetails = virtualMachineTO.getExternalDetails(); + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, + vmDetails); + PrepareExternalProvisioningCommand cmd = new PrepareExternalProvisioningCommand(virtualMachineTO); + cmd.setExternalDetails(externalDetails); + Answer answer = null; + CloudRuntimeException cre = new CloudRuntimeException("Failed to prepare VM"); + try { + answer = _agentMgr.send(host.getId(), cmd); + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.error("Failed PrepareExternalProvisioningCommand due to : {}", e.getMessage(), e); + throw cre; + } + if (answer == null) { + logger.error("Invalid answer received for PrepareExternalProvisioningCommand"); + throw cre; + } + if (!(answer instanceof PrepareExternalProvisioningAnswer)) { + logger.error("Unexpected answer received for PrepareExternalProvisioningCommand: [result: {}, details: {}]", + answer.getResult(), answer.getDetails()); + throw cre; + } + PrepareExternalProvisioningAnswer prepareAnswer = (PrepareExternalProvisioningAnswer)answer; + if (!prepareAnswer.getResult()) { + logger.error("Unexpected answer received for PrepareExternalProvisioningCommand: [result: {}, details: {}]", + answer.getResult(), answer.getDetails()); + throw cre; + } + updateExternalVmFromPrepareAnswer(virtualMachineTO, prepareAnswer.getVirtualMachineTO()); + } + @Override public void orchestrateStart(final String vmUuid, final Map params, final DeploymentPlan planToDeploy, final DeploymentPlanner planner) throws InsufficientCapacityException, ConcurrentOperationException, ResourceUnavailableException { @@ -1141,6 +1364,8 @@ public void orchestrateStart(final String vmUuid, final Map vols = _volsDao.findReadyRootVolumesByInstance(vm.getId()); @@ -1240,7 +1468,7 @@ public void orchestrateStart(final String vmUuid, final Map sshAccessDetails = _networkMgr.getSystemVMAccessDetails(vm); final Map ipAddressDetails = new HashMap<>(sshAccessDetails); ipAddressDetails.remove(NetworkElementCommand.ROUTER_NAME); StartCommand command = new StartCommand(vmTO, dest.getHost(), getExecuteInSequence(vm.getHypervisorType())); + updateStartCommandWithExternalDetails(dest.getHost(), vmTO, command); cmds.addCommand(command); vmGuru.finalizeDeployment(cmds, vmProfile, dest, ctx); @@ -1350,11 +1602,11 @@ public void orchestrateStart(final String vmUuid, final Map vmExternalDetails = vmTO.getExternalDetails(); + for (NicTO nic : vmTO.getNics()) { + if (!nic.isDefaultNic()) { + continue; + } + vmExternalDetails.put(VmDetailConstants.CLOUDSTACK_VLAN, networkService.getNicVlanValueForExternalVm(nic)); + } + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmExternalDetails); + command.setExternalDetails(externalDetails); + } + + protected void updateStopCommandForExternalHypervisorType(final HypervisorType hypervisorType, + final VirtualMachineProfile vmProfile, final StopCommand stopCommand) { + if (!HypervisorType.External.equals(hypervisorType) || vmProfile.getHostId() == null) { + return; + } + Host host = _hostDao.findById(vmProfile.getHostId()); + if (host == null) { + return; + } + VirtualMachineTO vmTO = ObjectUtils.defaultIfNull(stopCommand.getVirtualMachine(), toVmTO(vmProfile)); + if (MapUtils.isEmpty(vmTO.getGuestOsDetails())) { + vmTO.setGuestOsDetails(null); + } + if (MapUtils.isEmpty(vmTO.getExtraConfig())) { + vmTO.setExtraConfig(null); + } + if (MapUtils.isEmpty(vmTO.getNetworkIdToNetworkNameMap())) { + vmTO.setNetworkIdToNetworkNameMap(null); + } + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmTO.getExternalDetails()); + stopCommand.setVirtualMachine(vmTO); + stopCommand.setExternalDetails(externalDetails); + } + + protected void updateRebootCommandWithExternalDetails(Host host, VirtualMachineTO vmTO, RebootCommand rebootCmd) { + if (!HypervisorType.External.equals(host.getHypervisorType())) { + return; } + Map> externalDetails = extensionsManager.getExternalAccessDetails(host, vmTO.getExternalDetails()); + rebootCmd.setExternalDetails(externalDetails); } public void setVmNetworkDetails(VMInstanceVO vm, VirtualMachineTO vmTO) { @@ -1536,16 +1858,16 @@ private void updateOverCommitRatioForVmProfile(VirtualMachineProfile vmProfile, final ClusterDetailsVO clusterDetailRam = _clusterDetailsDao.findDetail(clusterId, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); final float parsedClusterCpuDetailCpu = Float.parseFloat(clusterDetailCpu.getValue()); final float parsedClusterDetailRam = Float.parseFloat(clusterDetailRam.getValue()); - UserVmDetailVO vmDetailCpu = userVmDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO); - UserVmDetailVO vmDetailRam = userVmDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); + VMInstanceDetailVO vmDetailCpu = vmInstanceDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO); + VMInstanceDetailVO vmDetailRam = vmInstanceDetailsDao.findDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO); if ((vmDetailCpu == null && parsedClusterCpuDetailCpu > 1f) || (vmDetailCpu != null && Float.parseFloat(vmDetailCpu.getValue()) != parsedClusterCpuDetailCpu)) { - userVmDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO, clusterDetailCpu.getValue(), true); + vmInstanceDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.CPU_OVER_COMMIT_RATIO, clusterDetailCpu.getValue(), true); } if ((vmDetailRam == null && parsedClusterDetailRam > 1f) || (vmDetailRam != null && Float.parseFloat(vmDetailRam.getValue()) != parsedClusterDetailRam)) { - userVmDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, clusterDetailRam.getValue(), true); + vmInstanceDetailsDao.addDetail(vmProfile.getId(), VmDetailConstants.MEMORY_OVER_COMMIT_RATIO, clusterDetailRam.getValue(), true); } vmProfile.setCpuOvercommitRatio(Float.parseFloat(clusterDetailCpu.getValue())); @@ -1761,7 +2083,7 @@ public boolean getExecuteInSequence(final HypervisorType hypervisorType) { } @Override - public boolean unmanage(String vmUuid) { + public Pair unmanage(String vmUuid, Long paramHostId) { VMInstanceVO vm = _vmDao.findByUuid(vmUuid); if (vm == null || vm.getRemoved() != null) { throw new CloudRuntimeException("Could not find VM with id = " + vmUuid); @@ -1774,6 +2096,10 @@ public boolean unmanage(String vmUuid) { throw new ConcurrentOperationException(msg); } + Long agentHostId = vm.getHostId(); + if (HypervisorType.KVM.equals(vm.getHypervisorType())) { + agentHostId = persistDomainForKVM(vm, paramHostId); + } Boolean result = Transaction.execute(new TransactionCallback() { @Override public Boolean doInTransaction(TransactionStatus status) { @@ -1797,21 +2123,66 @@ public Boolean doInTransaction(TransactionStatus status) { return true; } }); + HostVO host = ApiDBUtils.findHostById(agentHostId); + if (host == null) { + return new Pair<>(result, null); + } + logger.debug("Selected host UUID: {} to unmanage Instance: {}.", host.getUuid(), vm.getName()); + ActionEventUtils.onActionEvent(User.UID_SYSTEM, Account.ACCOUNT_ID_SYSTEM, Domain.ROOT_DOMAIN, EventTypes.EVENT_VM_UNMANAGE, + String.format("Successfully unmanaged Instance: %s (ID: %s) on host ID: %s", vm.getName(), vm.getUuid(), host.getUuid()), + vm.getId(), ApiCommandResourceType.VirtualMachine.toString()); + return new Pair<>(result, host.getUuid()); + } + + Long persistDomainForKVM(VMInstanceVO vm, Long paramHostId) { + Long agentHostId = vm.getHostId(); + String vmName = vm.getName(); + UnmanageInstanceCommand unmanageInstanceCommand; + if (State.Stopped.equals(vm.getState())) { + if (paramHostId == null) { + Pair clusterAndHostId = findClusterAndHostIdForVm(vm, false); + agentHostId = clusterAndHostId.second(); + if (agentHostId == null) { + String errorMsg = "No available host to persist domain XML for Instance: " + vmName; + logger.debug(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + } else { + agentHostId = paramHostId; + } + unmanageInstanceCommand = new UnmanageInstanceCommand(prepVmSpecForUnmanageCmd(vm.getId(), agentHostId)); // reconstruct vmSpec for stopped instance + } else { + unmanageInstanceCommand = new UnmanageInstanceCommand(vmName); + unmanageInstanceCommand.setConfigDriveAttached(vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.CONFIG_DRIVE_LOCATION) != null); + } - return BooleanUtils.isTrue(result); + logger.debug("Selected host ID: {} to persist domain XML for Instance: {}.", agentHostId, vmName); + try { + Answer answer = _agentMgr.send(agentHostId, unmanageInstanceCommand); + if (!answer.getResult()) { + String errorMsg = "Failed to persist domain XML for Instance: " + vmName + " on host ID: " + agentHostId; + logger.debug(errorMsg); + throw new CloudRuntimeException(errorMsg); + } + } catch (AgentUnavailableException | OperationTimedoutException e) { + String errorMsg = "Failed to send command to persist domain XML for Instance: " + vmName + " on host ID: " + agentHostId; + logger.error(errorMsg, e); + throw new CloudRuntimeException(errorMsg); + } + return agentHostId; } /** * Clean up VM snapshots (if any) from DB */ - private void unmanageVMSnapshots(VMInstanceVO vm) { + void unmanageVMSnapshots(VMInstanceVO vm) { _vmSnapshotMgr.deleteVMSnapshotsFromDB(vm.getId(), true); } /** * Clean up volumes for a VM to be unmanaged from CloudStack */ - private void unmanageVMVolumes(VMInstanceVO vm) { + void unmanageVMVolumes(VMInstanceVO vm) { final Long hostId = vm.getHostId() != null ? vm.getHostId() : vm.getLastHostId(); if (hostId != null) { volumeMgr.revokeAccess(vm.getId(), hostId); @@ -1829,7 +2200,7 @@ private void unmanageVMVolumes(VMInstanceVO vm) { * - If 'unmanage.vm.preserve.nics' = true: then the NICs are not removed but still Allocated, to preserve MAC addresses * - If 'unmanage.vm.preserve.nics' = false: then the NICs are removed while unmanaging */ - private void unmanageVMNics(VirtualMachineProfile profile, VMInstanceVO vm) { + void unmanageVMNics(VirtualMachineProfile profile, VMInstanceVO vm) { logger.debug("Cleaning up NICs of {}.", vm.toString()); Boolean preserveNics = UnmanagedVMsManager.UnmanageVMPreserveNic.valueIn(vm.getDataCenterId()); if (BooleanUtils.isTrue(preserveNics)) { @@ -1870,6 +2241,7 @@ protected boolean sendStop(final VirtualMachineGuru guru, final VirtualMachinePr final VirtualMachine vm = profile.getVirtualMachine(); Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); StopCommand stpCmd = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), checkBeforeCleanup); + updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stpCmd); if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { stpCmd.setVlanToPersistenceMap(vlanToPersistenceMap); } @@ -1894,9 +2266,7 @@ protected boolean sendStop(final VirtualMachineGuru guru, final VirtualMachinePr } final GPUDeviceTO gpuDevice = stop.getGpuDevice(); - if (gpuDevice != null) { - _resourceMgr.updateGPUDetails(vm.getHostId(), gpuDevice.getGroupDetails()); - } + _resourceMgr.updateGPUDetailsForVmStop(vm, gpuDevice); if (!answer.getResult()) { final String details = answer.getDetails(); logger.debug("Unable to stop VM due to {}", details); @@ -2000,7 +2370,7 @@ protected void releaseVmResources(final VirtualMachineProfile profile, final boo } try { - if (vm.getHypervisorType() != HypervisorType.BareMetal) { + if (vm.getHypervisorType() != HypervisorType.BareMetal && vm.getHypervisorType() != HypervisorType.External) { volumeMgr.release(profile); logger.debug("Successfully released storage resources for the VM {} in {} state", vm, state); } @@ -2197,6 +2567,7 @@ private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnabl Map vlanToPersistenceMap = getVlanToPersistenceMapForVM(vm.getId()); final StopCommand stop = new StopCommand(vm, getExecuteInSequence(vm.getHypervisorType()), false, cleanUpEvenIfUnableToStop); stop.setControlIp(getControlNicIpForVM(vm)); + updateStopCommandForExternalHypervisorType(vm.getHypervisorType(), profile, stop); if (MapUtils.isNotEmpty(vlanToPersistenceMap)) { stop.setVlanToPersistenceMap(vlanToPersistenceMap); } @@ -2224,9 +2595,7 @@ private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnabl } vmGuru.finalizeStop(profile, answer); final GPUDeviceTO gpuDevice = stop.getGpuDevice(); - if (gpuDevice != null) { - _resourceMgr.updateGPUDetails(vm.getHostId(), gpuDevice.getGroupDetails()); - } + _resourceMgr.updateGPUDetailsForVmStop(vm, gpuDevice); } else { throw new CloudRuntimeException("Invalid answer received in response to a StopCommand on " + vm.instanceName); } @@ -2246,6 +2615,14 @@ private void advanceStop(final VMInstanceVO vm, final boolean cleanUpEvenIfUnabl } else { logger.warn("Unable to actually stop {} but continue with release because it's a force stop", vm); vmGuru.finalizeStop(profile, answer); + if (HypervisorType.External.equals(profile.getHypervisorType())) { + try { + stateTransitTo(vm, VirtualMachine.Event.OperationSucceeded, null); + } catch (final NoTransitionException e) { + logger.warn("Unable to transition the state " + vm, e); + } + } + } } else { if (VirtualMachine.systemVMs.contains(vm.getType())) { @@ -2277,7 +2654,7 @@ public Boolean doInTransaction(TransactionStatus status) throws NoTransitionExce if (result && VirtualMachine.Type.User.equals(vm.type) && ResourceCountRunningVMsonly.value()) { ServiceOfferingVO offering = _offeringDao.findById(vm.getId(), vm.getServiceOfferingId()); VMTemplateVO template = _templateDao.findByIdIncludingRemoved(vm.getTemplateId()); - _resourceLimitMgr.decrementVmResourceCount(vm.getAccountId(), vm.isDisplay(), offering, template); + _resourceLimitMgr.decrementVmResourceCount(vm.getAccountId(), vm.isDisplay(), offering, template, null); } return result; } @@ -2321,6 +2698,7 @@ public boolean stateTransitTo(final VirtualMachine vm1, final VirtualMachine.Eve _reservationDao.setResourceId(Resource.ResourceType.user_vm, null); _reservationDao.setResourceId(Resource.ResourceType.cpu, null); _reservationDao.setResourceId(Resource.ResourceType.memory, null); + _reservationDao.setResourceId(Resource.ResourceType.gpu, null); } return _stateMachine.transitTo(vm, e, new Pair<>(vm.getHostId(), hostId), _vmDao); } @@ -2339,6 +2717,8 @@ public void destroy(final String vmUuid, final boolean expunge) throws AgentUnav deleteVMSnapshots(vm, expunge); + gpuService.deallocateAllGpuDevicesForVm(vm.getId()); + Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @Override public void doInTransactionWithoutResult(final TransactionStatus status) throws CloudRuntimeException { @@ -2349,6 +2729,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) throws throw new CloudRuntimeException("Unable to destroy " + vm); } else { if (expunge) { + backupManager.checkAndRemoveBackupOfferingBeforeExpunge(vm); if (!stateTransitTo(vm, VirtualMachine.Event.ExpungeOperation, vm.getHostId())) { logger.debug("Unable to expunge the vm because it is not in the correct state: {}", vm); throw new CloudRuntimeException("Unable to expunge " + vm); @@ -2541,6 +2922,7 @@ private void markVolumesInPool(VMInstanceVO vm, Answer[] hypervisorMigrationResu } volume.setPath(result.getPath()); volume.setPoolId(pool.getId()); + volume.setPoolType(pool.getPoolType()); if (result.getChainInfo() != null) { volume.setChainInfo(result.getChainInfo()); } @@ -2734,7 +3116,7 @@ private void orchestrateMigrate(final String vmUuid, final long srcHostId, final } protected void migrate(final VMInstanceVO vm, final long srcHostId, final DeployDestination dest) throws ResourceUnavailableException, ConcurrentOperationException { - logger.info("Migrating {} to {}", vm, dest); + logger.info("Start preparing migration of the VM: {} to {}", vm, dest); final long dstHostId = dest.getHost().getId(); final Host fromHost = _hostDao.findById(srcHostId); if (fromHost == null) { @@ -2784,6 +3166,9 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy updateOverCommitRatioForVmProfile(profile, dest.getHost().getClusterId()); final VirtualMachineTO to = toVmTO(profile); + + executePreMigrationCommand(vm, to, srcHostId); + final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to); setVmNetworkDetails(vm, to); @@ -2799,9 +3184,11 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy if (pfma == null || !pfma.getResult()) { final String details = pfma != null ? pfma.getDetails() : "null answer returned"; final String msg = "Unable to prepare for migration due to " + details; + logger.error("Failed to prepare destination host {} for migration of VM {} : {}", dstHostId, vm.getInstanceName(), details); pfma = null; throw new AgentUnavailableException(msg, dstHostId); } + logger.debug("Successfully prepared destination host {} for migration of VM {} ", dstHostId, vm.getInstanceName()); } catch (final OperationTimedoutException e1) { throw new AgentUnavailableException("Operation timed out", dstHostId); } finally { @@ -2822,18 +3209,23 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy volumeMgr.release(vm.getId(), dstHostId); } - logger.info("Migration cancelled because state has changed: {}", vm); - throw new ConcurrentOperationException("Migration cancelled because state has changed: " + vm); + String msg = "Migration cancelled because state has changed: " + vm; + logger.warn(msg); + throw new ConcurrentOperationException(msg); } } catch (final NoTransitionException e1) { _networkMgr.rollbackNicForMigration(vmSrc, profile); volumeMgr.release(vm.getId(), dstHostId); - logger.info("Migration cancelled because {}", e1.getMessage()); + String msg = String.format("Migration cancelled for VM %s due to state transition failure: %s", + vm.getInstanceName(), e1.getMessage()); + logger.warn(msg, e1); throw new ConcurrentOperationException("Migration cancelled because " + e1.getMessage()); } catch (final CloudRuntimeException e2) { _networkMgr.rollbackNicForMigration(vmSrc, profile); volumeMgr.release(vm.getId(), dstHostId); - logger.info("Migration cancelled because {}", e2.getMessage()); + String msg = String.format("Migration cancelled for VM %s due to runtime exception: %s", + vm.getInstanceName(), e2.getMessage()); + logger.error(msg, e2); work.setStep(Step.Done); _workDao.update(work.getId(), work); try { @@ -2853,14 +3245,36 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy final Answer ma = _agentMgr.send(vm.getLastHostId(), mc); if (ma == null || !ma.getResult()) { final String details = ma != null ? ma.getDetails() : "null answer returned"; + String msg = String.format("Migration command failed for VM %s on source host id=%s to destination host %s: %s", + vm.getInstanceName(), vm.getLastHostId(), dstHostId, details); + logger.error(msg); throw new CloudRuntimeException(details); } + logger.info("Migration command successful for VM {}", vm.getInstanceName()); } catch (final OperationTimedoutException e) { - if (e.isActive()) { - logger.warn("Active migration command so scheduling a restart for {}", vm, e); - _haMgr.scheduleRestart(vm, true); + boolean success = false; + if (HypervisorType.KVM.equals(vm.getHypervisorType())) { + try { + final Answer answer = _agentMgr.send(vm.getHostId(), new CheckVirtualMachineCommand(vm.getInstanceName())); + if (answer != null && answer.getResult() && answer instanceof CheckVirtualMachineAnswer) { + final CheckVirtualMachineAnswer vmAnswer = (CheckVirtualMachineAnswer) answer; + if (VirtualMachine.PowerState.PowerOn.equals(vmAnswer.getState())) { + logger.info(String.format("Vm %s is found on destination host %s. Migration is successful", vm, vm.getHostId())); + success = true; + } + } + } catch (Exception ex) { + logger.error(String.format("Failed to get state of VM %s on destination host %s: %s", vm, vm.getHostId(), ex.getMessage())); + } + } + if (!success) { + if (e.isActive()) { + logger.warn("Active migration command so scheduling a restart for {}", vm, e); + _haMgr.scheduleRestart(vm, true); + + throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId); + } } - throw new AgentUnavailableException("Operation timed out on migrating " + vm, dstHostId); } try { @@ -2873,7 +3287,7 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy try { if (!checkVmOnHost(vm, dstHostId)) { - logger.error("Unable to complete migration for {}", vm); + logger.error("Migration verification failed for VM {} : VM not found on destination host {} ", vm.getInstanceName(), dstHostId); try { _agentMgr.send(srcHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null); } catch (final AgentUnavailableException e) { @@ -2886,11 +3300,14 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy logger.warn("Error while checking the vm {} on host {}", vm, dest.getHost(), e); } migrated = true; + executePostMigrationCommand(vm, to, dstHostId); } finally { if (!migrated) { - logger.info("Migration was unsuccessful. Cleaning up: {}", vm); + logger.info("Migration was unsuccessful. Cleaning up: {}", vm); _networkMgr.rollbackNicForMigration(vmSrc, profile); volumeMgr.release(vm.getId(), dstHostId); + // deallocate GPU devices for the VM on the destination host + gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), dstHostId); _alertMgr.sendAlert(alertType, fromHost.getDataCenterId(), fromHost.getPodId(), "Unable to migrate vm " + vm.getInstanceName() + " from host " + fromHost.getName() + " in zone " + dest.getDataCenter().getName() + " and pod " + @@ -2898,7 +3315,7 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy try { _agentMgr.send(dstHostId, new Commands(cleanup(vm, dpdkInterfaceMapping)), null); } catch (final AgentUnavailableException ae) { - logger.warn("Looks like the destination Host is unavailable for cleanup", ae); + logger.warn("Destination host {} unavailable for cleanup after failed migration of VM {}", dstHostId, vm.getInstanceName(), ae); } _networkMgr.setHypervisorHostname(profile, dest, false); try { @@ -2907,9 +3324,13 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy logger.warn(e.getMessage()); } } else { + logger.info("Migration completed successfully for VM %s" + vm); _networkMgr.commitNicForMigration(vmSrc, profile); volumeMgr.release(vm.getId(), srcHostId); + // deallocate GPU devices for the VM on the src host after migration is complete + gpuService.deallocateGpuDevicesForVmOnHost(vm.getId(), srcHostId); _networkMgr.setHypervisorHostname(profile, dest, true); + recreateCheckpointsKvmOnVmAfterMigration(vm, dstHostId); updateVmPod(vm, dstHostId); } @@ -2919,6 +3340,30 @@ protected void migrate(final VMInstanceVO vm, final long srcHostId, final Deploy } } + private void executePostMigrationCommand(VMInstanceVO vm, VirtualMachineTO to, long dstHostId) { + if (!(vm.getHypervisorType() == HypervisorType.KVM && hasClvmVolumes(vm.getId()))) { + return; + } + final String dstHostUuid = _hostDao.findById(dstHostId).getUuid(); + try { + logger.info("Executing post-migration tasks for VM {} with CLVM volumes on destination host {}", vm.getInstanceName(), dstHostUuid); + final PostMigrationCommand postMigrationCommand = new PostMigrationCommand(to, vm.getInstanceName()); + final Answer postMigrationAnswer = _agentMgr.send(dstHostId, postMigrationCommand); + + if (postMigrationAnswer == null || !postMigrationAnswer.getResult()) { + final String details = postMigrationAnswer != null ? postMigrationAnswer.getDetails() : "null answer returned"; + logger.warn("Post-migration tasks failed for VM {} on destination host {}: {}. Migration completed but some cleanup may be needed.", + vm.getInstanceName(), dstHostUuid, details); + } else { + logger.info("Successfully completed post-migration tasks for VM {} on destination host {}", vm.getInstanceName(), dstHostUuid); + } + } catch (Exception e) { + logger.warn("Exception during post-migration tasks for VM {} on destination host {}: {}. Migration completed but some cleanup may be needed.", + vm.getInstanceName(), dstHostUuid, e.getMessage(), e); + } + updateClvmLockHostForVmVolumes(vm.getId(), dstHostId); + } + /** * Create and set parameters for the {@link MigrateCommand} used in the migration and scaling of VMs. */ @@ -2934,6 +3379,7 @@ protected MigrateCommand buildMigrateCommand(VMInstanceVO vmInstance, VirtualMac migrateCommand.setVlanToPersistenceMap(vlanToPersistenceMap); } + logger.debug("Setting auto convergence to: {}", StorageManager.KvmAutoConvergence.value()); migrateCommand.setAutoConvergence(StorageManager.KvmAutoConvergence.value()); migrateCommand.setHostGuid(destination.getHost().getGuid()); @@ -2964,6 +3410,27 @@ private void updateVmPod(VMInstanceVO vm, long dstHostId) { _vmDao.persist(newVm); } + /** + * Updates CLVM_LOCK_HOST_ID for all CLVM volumes attached to a VM after VM migration. + * This ensures that subsequent operations on CLVM volumes are routed to the correct host. + * + * @param vmId The ID of the VM that was migrated + * @param destHostId The destination host ID where the VM now resides + */ + private void updateClvmLockHostForVmVolumes(long vmId, long destHostId) { + List volumes = _volsDao.findByInstance(vmId); + if (CollectionUtils.isEmpty(volumes)) { + return; + } + + for (VolumeVO volume : volumes) { + StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); + if (pool != null && ClvmPoolManager.isClvmPoolType(pool.getPoolType())) { + clvmPoolManager.setClvmLockHostId(volume.getId(), destHostId); + } + } + } + /** * We create the mapping of volumes and storage pool to migrate the VMs according to the information sent by the user. * If the user did not enter a complete mapping, the volumes that were left behind will be auto mapped using {@link #createStoragePoolMappingsForVolumes(VirtualMachineProfile, DataCenterDeployment, Map, List)} @@ -3084,7 +3551,8 @@ protected void createStoragePoolMappingsForVolumes(VirtualMachineProfile profile protected boolean shouldMapVolume(VirtualMachineProfile profile, StoragePoolVO currentPool) { boolean isManaged = currentPool.isManaged(); boolean isNotKvm = HypervisorType.KVM != profile.getHypervisorType(); - return isNotKvm || isManaged; + boolean isClvm = ClvmPoolManager.isClvmPoolType(currentPool.getPoolType()); + return isNotKvm || isManaged || isClvm; } /** @@ -3288,7 +3756,7 @@ private void orchestrateMigrateWithStorage(final String vmUuid, final long srcHo if (defaultNic != null && VirtualMachine.Type.User.equals(vm.getType())) { UserVmVO userVm = _userVmDao.findById(vm.getId()); - Map details = userVmDetailsDao.listDetailsKeyPairs(vm.getId()); + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); userVm.setDetails(details); Network network = _networkModel.getNetwork(defaultNic.getNetworkId()); @@ -3359,6 +3827,7 @@ private void orchestrateMigrateWithStorage(final String vmUuid, final long srcHo _networkMgr.commitNicForMigration(vmSrc, profile); volumeMgr.release(vm.getId(), srcHostId); _networkMgr.setHypervisorHostname(profile, destination, true); + endSnapshotChainForVolumes(volumeToPoolMap, vm.getHypervisorType()); } work.setStep(Step.Done); @@ -3366,6 +3835,68 @@ private void orchestrateMigrateWithStorage(final String vmUuid, final long srcHo } } + protected void endSnapshotChainForVolumes(Map volumeToPoolMap, HypervisorType hypervisorType) { + Set volumes = volumeToPoolMap.keySet(); + volumes.forEach(volume -> { + Volume volumeOnDestination = _volsDao.findByPoolIdName(volumeToPoolMap.get(volume).getId(), volume.getName()); + snapshotManager.endSnapshotChainForVolume(volumeOnDestination.getId(), hypervisorType); + }); + } + + protected void recreateCheckpointsKvmOnVmAfterMigration(VMInstanceVO vm, long hostId) { + if (!HypervisorType.KVM.equals(vm.getHypervisorType())) { + logger.debug("Will not recreate checkpoint on VM as it is not running on KVM, thus it is not needed."); + return; + } + + List volumes = getVmVolumesWithCheckpointsToRecreate(vm); + + if (volumes.isEmpty()) { + logger.debug("Will not recreate checkpoints on VM as its volumes do not have any checkpoints associated with them."); + return; + } + + RecreateCheckpointsCommand recreateCheckpointsCommand = new RecreateCheckpointsCommand(volumes, vm.getInstanceName()); + Answer answer = null; + try { + logger.debug(String.format("Recreating the volume checkpoints with URLs [%s] of volumes [%s] on %s as part of the migration process.", volumes.stream().map(VolumeObjectTO::getCheckpointPaths).collect(Collectors.toList()), volumes, vm)); + answer = _agentMgr.send(hostId, recreateCheckpointsCommand); + } catch (AgentUnavailableException | OperationTimedoutException e) { + logger.error(String.format("Exception while sending command to host [%s] to recreate checkpoints with URLs [%s] of volumes [%s] on %s due to: [%s].", hostId, volumes.stream().map(VolumeObjectTO::getCheckpointPaths).collect(Collectors.toList()), volumes, vm, e.getMessage()), e); + throw new CloudRuntimeException(e); + } finally { + if (answer != null && answer.getResult()) { + logger.debug(String.format("Successfully recreated checkpoints on VM [%s].", vm)); + return; + } + + logger.debug(String.format("Migration on VM [%s] was successful; however, we weren't able to recreate the checkpoints on it. Marking the snapshot chain as ended." + + " Next snapshot will create a new snapshot chain.", vm)); + + volumes.forEach(volumeObjectTO -> snapshotManager.endSnapshotChainForVolume(volumeObjectTO.getId(), HypervisorType.KVM)); + } + } + + + protected List getVmVolumesWithCheckpointsToRecreate(VMInstanceVO vm) { + List vmVolumes = _volsDao.findByInstance(vm.getId()); + List volumes = new ArrayList<>(); + + for (VolumeVO volume : vmVolumes) { + Pair, Set> volumeCheckpointPathsAndImageStoreUrls = volumeMgr.getVolumeCheckpointPathsAndImageStoreUrls(volume.getId(), HypervisorType.KVM); + if (volumeCheckpointPathsAndImageStoreUrls.first().isEmpty()) { + continue; + } + VolumeObjectTO volumeTo = new VolumeObjectTO(); + volumeTo.setCheckpointPaths(volumeCheckpointPathsAndImageStoreUrls.first()); + volumeTo.setCheckpointImageStoreUrls(volumeCheckpointPathsAndImageStoreUrls.second()); + volumeTo.setPath(volume.getPath()); + volumes.add(volumeTo); + } + return volumes; + } + + @Override public VirtualMachineTO toVmTO(final VirtualMachineProfile profile) { final HypervisorGuru hvGuru = _hvGuruMgr.getGuru(profile.getVirtualMachine().getHypervisorType()); @@ -3559,19 +4090,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 { @@ -3631,6 +4149,7 @@ private void orchestrateReboot(final String vmUuid, final Map nics = _nicsDao.listByVmId(vmProfile.getId()); + Collections.sort(nics, (nic1, nic2) -> { + Long nicId1 = Long.valueOf(nic1.getDeviceId()); + Long nicId2 = Long.valueOf(nic2.getDeviceId()); + return nicId1.compareTo(nicId2); + }); + + for (final NicVO nic : nics) { + final Network network = _networkModel.getNetwork(nic.getNetworkId()); + final NicProfile nicProfile = + new NicProfile(nic, network, nic.getBroadcastUri(), nic.getIsolationUri(), null, _networkModel.isSecurityGroupSupportedInNetwork(network), + _networkModel.getNetworkTag(vmProfile.getHypervisorType(), network)); + vmProfile.addNic(nicProfile); + } + + List volumes = _volsDao.findUsableVolumesForInstance(vmId); + for (VolumeVO vol: volumes) { + VolumeInfo volumeInfo = volumeDataFactory.getVolume(vol.getId()); + DataTO dataTO = volumeInfo.getTO(); + DiskTO disk = storageMgr.getDiskWithThrottling(dataTO, vol.getVolumeType(), vol.getDeviceId(), vol.getPath(), vm.getServiceOfferingId(), vol.getDiskOfferingId()); + vmProfile.addDisk(disk); + } + + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId, + List.of(VirtualMachineProfile.Param.BootType.getName(), VirtualMachineProfile.Param.BootMode.getName(), + VirtualMachineProfile.Param.UefiFlag.getName())); + + if (details.containsKey(VirtualMachineProfile.Param.BootType.getName())) { + vmProfile.getParameters().put(VirtualMachineProfile.Param.BootType, details.get(VirtualMachineProfile.Param.BootType.getName())); + } + + if (details.containsKey(VirtualMachineProfile.Param.BootMode.getName())) { + vmProfile.getParameters().put(VirtualMachineProfile.Param.BootMode, details.get(VirtualMachineProfile.Param.BootMode.getName())); + } + + if (details.containsKey(VirtualMachineProfile.Param.UefiFlag.getName())) { + vmProfile.getParameters().put(VirtualMachineProfile.Param.UefiFlag, details.get(VirtualMachineProfile.Param.UefiFlag.getName())); + } + + return toVmTO(vmProfile); + } + protected VirtualMachineTO getVmTO(Long vmId) { final VMInstanceVO vm = _vmDao.findById(vmId); final VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); @@ -3951,11 +4529,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())) { @@ -4415,6 +4988,12 @@ private void orchestrateMigrateForScale(final String vmUuid, final long srcHostI volumeMgr.prepareForMigration(profile, dest); final VirtualMachineTO to = toVmTO(profile); + + // Step 1: Send PreMigrationCommand to source host to convert CLVM volumes to shared mode + // This must happen BEFORE PrepareForMigrationCommand on destination to avoid lock conflicts + executePreMigrationCommand(vm, to, srcHostId); + + // Step 2: Send PrepareForMigrationCommand to destination host final PrepareForMigrationCommand pfmc = new PrepareForMigrationCommand(to); ItWorkVO work = new ItWorkVO(UUID.randomUUID().toString(), _nodeId, State.Migrating, vm.getType(), vm.getId()); @@ -4499,6 +5078,7 @@ private void orchestrateMigrateForScale(final String vmUuid, final long srcHostI } migrated = true; + executePostMigrationCommand(vm, to, dstHostId); } finally { if (!migrated) { logger.info("Migration was unsuccessful. Cleaning up: {}", vm); @@ -4662,7 +5242,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) { @@ -4675,22 +5255,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()); @@ -4719,16 +5306,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); } @@ -4743,20 +5334,20 @@ private VMInstanceVO orchestrateReConfigureVm(String vmUuid, ServiceOffering old } private void removeCustomOfferingDetails(long vmId) { - Map details = userVmDetailsDao.listDetailsKeyPairs(vmId); + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); details.remove(UsageEventVO.DynamicParameters.cpuNumber.name()); details.remove(UsageEventVO.DynamicParameters.cpuSpeed.name()); details.remove(UsageEventVO.DynamicParameters.memory.name()); - List detailList = new ArrayList<>(); + List detailList = new ArrayList<>(); for(Map.Entry entry: details.entrySet()) { - UserVmDetailVO detailVO = new UserVmDetailVO(vmId, entry.getKey(), entry.getValue(), true); + VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); detailList.add(detailVO); } - userVmDetailsDao.saveDetails(detailList); + vmInstanceDetailsDao.saveDetails(detailList); } private void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOffering) { - Map details = userVmDetailsDao.listDetailsKeyPairs(vmId); + Map details = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); // We need to restore only the customizable parameters. If we save a parameter that is not customizable and attempt // to restore a VM snapshot, com.cloud.vm.UserVmManagerImpl.validateCustomParameters will fail. @@ -4771,12 +5362,12 @@ private void saveCustomOfferingDetails(long vmId, ServiceOffering serviceOfferin details.put(UsageEventVO.DynamicParameters.memory.name(), serviceOffering.getRamSize().toString()); } - List detailList = new ArrayList<>(); + List detailList = new ArrayList<>(); for (Map.Entry entry: details.entrySet()) { - UserVmDetailVO detailVO = new UserVmDetailVO(vmId, entry.getKey(), entry.getValue(), true); + VMInstanceDetailVO detailVO = new VMInstanceDetailVO(vmId, entry.getKey(), entry.getValue(), true); detailList.add(detailVO); } - userVmDetailsDao.saveDetails(detailList); + vmInstanceDetailsDao.saveDetails(detailList); } @Override @@ -4791,7 +5382,7 @@ public ConfigKey[] getConfigKeys() { VmConfigDriveLabel, VmConfigDriveOnPrimaryPool, VmConfigDriveForceHostCacheUse, VmConfigDriveUseHostCacheOnUnsupportedPool, HaVmRestartHostUp, ResourceCountRunningVMsonly, AllowExposeHypervisorHostname, AllowExposeHypervisorHostnameAccountLevel, SystemVmRootDiskSize, AllowExposeDomainInMetadata, MetadataCustomCloudName, VmMetadataManufacturer, VmMetadataProductName, - VmSyncPowerStateTransitioning + VmSyncPowerStateTransitioning, SystemVmEnableUserData }; } @@ -5799,6 +6390,80 @@ private Pair orchestrateUpdateDefaultNic(final VmWorkUpd _jobMgr.marshallResultObject(result)); } + @Override + public boolean updateVmNic(VirtualMachine vm, Nic nic, Boolean enabled) { + Outcome outcome = updateVmNicThroughJobQueue(vm, nic, enabled); + + retrieveVmFromJobOutcome(outcome, vm.getUuid(), "updateVmNic"); + + try { + Object jobResult = retrieveResultFromJobOutcomeAndThrowExceptionIfNeeded(outcome); + if (jobResult instanceof Boolean) { + return BooleanUtils.isTrue((Boolean) jobResult); + } + } catch (ResourceUnavailableException | InsufficientCapacityException ex) { + throw new CloudRuntimeException(String.format("Exception while updating VM [%s] NIC. Check the logs for more information.", vm.getUuid())); + } + throw new CloudRuntimeException("Unexpected job execution result."); + } + + private boolean orchestrateUpdateVmNic(final VirtualMachine vm, final Nic nic, final Boolean enabled) throws ResourceUnavailableException { + if (vm.getState() == State.Running) { + try { + UpdateVmNicCommand updateVmNicCmd = new UpdateVmNicCommand(nic.getMacAddress(), vm.getName(), enabled); + Commands cmds = new Commands(Command.OnError.Stop); + cmds.addCommand("updatevmnic", updateVmNicCmd); + + _agentMgr.send(vm.getHostId(), cmds); + + UpdateVmNicAnswer updateVmNicAnswer = cmds.getAnswer(UpdateVmNicAnswer.class); + if (updateVmNicAnswer == null || !updateVmNicAnswer.getResult()) { + logger.warn("Unable to update VM %s NIC [{}].", vm.getName(), nic.getUuid()); + return false; + } + } catch (final OperationTimedoutException e) { + throw new AgentUnavailableException(String.format("Unable to update NIC %s for VM %s.", nic.getUuid(), vm.getUuid()), vm.getHostId(), e); + } + } + + NicVO nicVo = _nicsDao.findById(nic.getId()); + nicVo.setEnabled(enabled); + _nicsDao.persist(nicVo); + + return true; + } + + public Outcome updateVmNicThroughJobQueue(final VirtualMachine vm, final Nic nic, final Boolean isNicEnabled) { + Long vmId = vm.getId(); + String commandName = VmWorkUpdateNic.class.getName(); + Pair pendingWorkJob = retrievePendingWorkJob(vmId, commandName); + + VmWorkJobVO workJob = pendingWorkJob.first(); + + if (workJob == null) { + Pair newVmWorkJobAndInfo = createWorkJobAndWorkInfo(commandName, vmId); + + workJob = newVmWorkJobAndInfo.first(); + VmWorkUpdateNic workInfo = new VmWorkUpdateNic(newVmWorkJobAndInfo.second(), nic.getId(), isNicEnabled); + + setCmdInfoAndSubmitAsyncJob(workJob, workInfo, vmId); + } + AsyncJobExecutionContext.getCurrentExecutionContext().joinJob(workJob.getId()); + + return new VmJobVirtualMachineOutcome(workJob, vmId); + } + + @ReflectionUse + private Pair orchestrateUpdateVmNic(final VmWorkUpdateNic work) throws Exception { + VMInstanceVO vm = findVmById(work.getVmId()); + final NicVO nic = _entityMgr.findById(NicVO.class, work.getNicId()); + if (nic == null) { + throw new CloudRuntimeException(String.format("Unable to find NIC with ID %s.", work.getNicId())); + } + final boolean result = orchestrateUpdateVmNic(vm, nic, work.isEnabled()); + return new Pair<>(JobInfo.Status.SUCCEEDED, _jobMgr.marshallResultObject(result)); + } + private Pair findClusterAndHostIdForVmFromVolumes(long vmId) { Long clusterId = null; Long hostId = null; @@ -5840,14 +6505,46 @@ public Pair findClusterAndHostIdForVm(VirtualMachine vm, boolean ski host = host == null ? _hostDao.findById(hostId) : host; if (host != null) { clusterId = host.getClusterId(); + return new Pair<>(clusterId, hostId); } - return new Pair<>(clusterId, hostId); + return findClusterAndHostIdForVmFromVolumes(vm.getId()); } private Pair findClusterAndHostIdForVm(VirtualMachine vm) { return findClusterAndHostIdForVm(vm, false); } + private boolean hasClvmVolumes(long vmId) { + List volumes = _volsDao.findByInstance(vmId); + return volumes.stream() + .map(v -> _storagePoolDao.findById(v.getPoolId())) + .anyMatch(pool -> pool != null && ClvmPoolManager.isClvmPoolType(pool.getPoolType())); + } + + private void executePreMigrationCommand(VMInstanceVO vm, VirtualMachineTO to, long srcHostId) { + if (!(vm.getHypervisorType() == HypervisorType.KVM && hasClvmVolumes(vm.getId()))) { + return; + } + final String vmInstanceName = vm.getInstanceName(); + final String srcHostUuid = _hostDao.findById(srcHostId).getUuid(); + logger.info("Sending PreMigrationCommand to source host {} for VM {} with CLVM volumes", srcHostUuid, vmInstanceName); + final PreMigrationCommand preMigCmd = new PreMigrationCommand(to, vmInstanceName); + Answer preMigAnswer = null; + try { + preMigAnswer = _agentMgr.send(srcHostId, preMigCmd); + if (preMigAnswer == null || !preMigAnswer.getResult()) { + final String details = preMigAnswer != null ? preMigAnswer.getDetails() : "null answer returned"; + final String msg = "Failed to prepare source host for migration: " + details; + logger.error("Failed to prepare source host {} for migration of VM {}: {}", srcHostUuid, vmInstanceName, details); + throw new CloudRuntimeException(msg); + } + logger.info("Successfully prepared source host {} for migration of VM {}", srcHostUuid, vmInstanceName); + } catch (final AgentUnavailableException | OperationTimedoutException e) { + logger.error("Failed to send PreMigrationCommand to source host {}: {}", srcHostUuid, e.getMessage(), e); + throw new CloudRuntimeException("Failed to prepare source host for migration: " + e.getMessage(), e); + } + } + @Override public Pair findClusterAndHostIdForVm(long vmId) { VMInstanceVO vm = _vmDao.findById(vmId); @@ -6094,7 +6791,7 @@ protected boolean isDiskOfferingSuitableForVm(VMInstanceVO vm, VirtualMachinePro @Override public Map getDiskOfferingSuitabilityForVm(long vmId, List diskOfferingIds) { VMInstanceVO vm = _vmDao.findById(vmId); - if (userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_VM) != null) { + if (vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_VM) != null) { return new HashMap<>(); } VirtualMachineProfile profile = new VirtualMachineProfileImpl(vm); @@ -6121,4 +6818,18 @@ public void checkDeploymentPlan(VirtualMachine virtualMachine, VirtualMachineTem vmProfile), DataCenter.class, plan.getDataCenterId(), areAffinityGroupsAssociated(vmProfile)); } } + + @Override + public boolean isBlankInstanceDefaultTemplate(VirtualMachineTemplate template) { + return KVM_BLANK_VM_TEMPLATE_NAME.equals(template.getUniqueName()); + } + + @Override + public boolean isBlankInstance(VirtualMachineTemplate template) { + if (isBlankInstanceDefaultTemplate(template)) { + return true; + } + return Boolean.TRUE.equals( + MapUtils.getBoolean(CallContext.current().getContextParameters(), ApiConstants.BLANK_INSTANCE)); + } } diff --git a/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java new file mode 100644 index 000000000000..1c63cf34a192 --- /dev/null +++ b/engine/orchestration/src/main/java/com/cloud/vm/VmWorkUpdateNic.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +public class VmWorkUpdateNic extends VmWork { + private static final long serialVersionUID = -8957066627929113278L; + + Long nicId; + Boolean enabled; + + public VmWorkUpdateNic(VmWork vmWork, Long nicId, Boolean enabled) { + super(vmWork); + this.nicId = nicId; + this.enabled = enabled; + } + + public Long getNicId() { + return nicId; + } + + public Boolean isEnabled() { + return enabled; + } +} diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java index 19b0e773cd01..39ab83fab600 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineClusterVO.java @@ -114,6 +114,9 @@ public class EngineClusterVO implements EngineCluster, Identity { @Column(name = "engine_state", updatable = true, nullable = false, length = 32) protected State state = null; + @Column(name = "storage_access_groups") + String storageAccessGroups; + public EngineClusterVO() { clusterType = Cluster.ClusterType.CloudManaged; allocationState = Grouping.AllocationState.Enabled; @@ -176,6 +179,11 @@ public ManagedState getManagedState() { return managedState; } + @Override + public String getStorageAccessGroups() { + return storageAccessGroups; + } + public void setManagedState(ManagedState managedState) { this.managedState = managedState; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java index 95931d5b72d5..cd3f6b857a29 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostPodVO.java @@ -89,6 +89,9 @@ public class EngineHostPodVO implements EnginePod, Identity { @Temporal(value = TemporalType.TIMESTAMP) protected Date lastUpdated; + @Column(name = "storage_access_groups") + String storageAccessGroups; + /** * Note that state is intentionally missing the setter. Any updates to * the state machine needs to go through the DAO object because someone @@ -202,6 +205,11 @@ public boolean getExternalDhcp() { return externalDhcp; } + @Override + public String getStorageAccessGroups() { + return storageAccessGroups; + } + public void setExternalDhcp(boolean use) { externalDhcp = use; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java index 053d9ac218ee..eec2b011b3e8 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/EngineHostVO.java @@ -372,6 +372,9 @@ public void setHostTags(List hostTags) { @Column(name = "mgmt_server_id") private Long managementServerId; + @Column(name = "last_mgmt_server_id") + private Long lastManagementServerId; + @Column(name = "dom0_memory") private long dom0MinMemory; @@ -402,6 +405,9 @@ public void setHostTags(List hostTags) { @Column(name = "engine_state", updatable = true, nullable = false, length = 32) protected State orchestrationState = null; + @Column(name = "storage_access_groups") + private String storageAccessGroups = null; + public EngineHostVO(String guid) { this.guid = guid; this.status = Status.Creating; @@ -556,6 +562,10 @@ public void setManagementServerId(Long managementServerId) { this.managementServerId = managementServerId; } + public void setLastManagementServerId(Long lastManagementServerId) { + this.lastManagementServerId = lastManagementServerId; + } + @Override public long getLastPinged() { return lastPinged; @@ -625,6 +635,11 @@ public Long getManagementServerId() { return managementServerId; } + @Override + public Long getLastManagementServerId() { + return lastManagementServerId; + } + @Override public Date getDisconnectedOn() { return disconnectedOn; @@ -795,4 +810,13 @@ public State getOrchestrationState() { public PartitionType partitionType() { return PartitionType.Host; } + + @Override + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java index 2ad8d15d0b71..7f6571becc83 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/datacenter/entity/api/db/dao/EngineHostDaoImpl.java @@ -213,7 +213,6 @@ public EngineHostDaoImpl() { SequenceSearch = createSearchBuilder(); SequenceSearch.and("id", SequenceSearch.entity().getId(), SearchCriteria.Op.EQ); - // SequenceSearch.addRetrieve("sequence", SequenceSearch.entity().getSequence()); SequenceSearch.done(); DirectlyConnectedSearch = createSearchBuilder(); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java index 6763a13aed63..9f6d02cc1234 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/CloudOrchestrator.java @@ -18,6 +18,10 @@ */ package org.apache.cloudstack.engine.orchestration; +import com.cloud.storage.Snapshot; +import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.Volume; +import com.cloud.template.VirtualMachineTemplate; import java.net.URL; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -57,8 +61,9 @@ import com.cloud.vm.NicProfile; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachineManager; +import com.cloud.vm.VmDiskInfo; import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; import static org.apache.cloudstack.api.ApiConstants.MAX_IOPS; @@ -83,7 +88,7 @@ public class CloudOrchestrator implements OrchestrationService { protected UserVmDao _userVmDao = null; @Inject - protected UserVmDetailsDao _userVmDetailsDao = null; + protected VMInstanceDetailsDao _vmInstanceDetailsDao = null; @Inject protected ServiceOfferingDao _serviceOfferingDao; @@ -158,8 +163,9 @@ public void destroyVolume(String volumeEntity) { @Override public VirtualMachineEntity createVirtualMachine(String id, String owner, String templateId, String hostName, String displayName, String hypervisor, int cpu, - int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, - Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId) throws InsufficientCapacityException { + int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, + Long rootDiskSize, Map> extraDhcpOptionMap, Map dataDiskTemplateToDiskOfferingMap, Long dataDiskOfferingId, Long rootDiskOfferingId, + Long rootDiskKmsKeyId, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, // vmEntityManager); @@ -184,7 +190,6 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String // Else, a disk offering is optional, and if present will be used to create the data disk DiskOfferingInfo rootDiskOfferingInfo = new DiskOfferingInfo(); - List dataDiskOfferings = new ArrayList(); ServiceOfferingVO computeOffering = _serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); @@ -194,9 +199,10 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String } rootDiskOfferingInfo.setDiskOffering(rootDiskOffering); rootDiskOfferingInfo.setSize(rootDiskSize); + rootDiskOfferingInfo.setKmsKeyId(rootDiskKmsKeyId); if (rootDiskOffering.isCustomizedIops() != null && rootDiskOffering.isCustomizedIops()) { - Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); + Map userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); if (userVmDetails != null) { String minIops = userVmDetails.get(MIN_IOPS); @@ -207,6 +213,8 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String } } + List dataDiskOfferings = new ArrayList(); + List dataDiskDeviceIds = new ArrayList<>(); if (dataDiskOfferingId != null) { DiskOfferingVO diskOffering = _diskOfferingDao.findById(dataDiskOfferingId); if (diskOffering == null) { @@ -228,7 +236,7 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String dataDiskOfferingInfo.setSize(size); if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { - Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); + Map userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); if (userVmDetails != null) { String minIops = userVmDetails.get("minIopsDo"); @@ -240,6 +248,12 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String } dataDiskOfferings.add(dataDiskOfferingInfo); + dataDiskDeviceIds.add(null); + } + } else if (dataDiskInfoList != null){ + dataDiskOfferings.addAll(dataDiskInfoList); + for (VmDiskInfo dataDiskInfo : dataDiskInfoList) { + dataDiskDeviceIds.add(dataDiskInfo.getDeviceId()); } } @@ -254,9 +268,13 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String } } } - - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(templateId)), computeOffering, rootDiskOfferingInfo, dataDiskOfferings, networkIpMap, plan, - hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap); + VirtualMachineTemplate template = null; + if (volume != null || snapshot != null) { + template = _templateDao.findByIdIncludingRemoved(new Long(templateId)); + } else + template = _templateDao.findById(new Long(templateId)); + _itMgr.allocate(vm.getInstanceName(), template, computeOffering, rootDiskOfferingInfo, dataDiskOfferings, dataDiskDeviceIds, + networkIpMap, plan, hypervisorType, extraDhcpOptionMap, dataDiskTemplateToDiskOfferingMap, volume, snapshot); return vmEntity; } @@ -264,7 +282,7 @@ public VirtualMachineEntity createVirtualMachine(String id, String owner, String @Override public VirtualMachineEntity createVirtualMachineFromScratch(String id, String owner, String isoId, String hostName, String displayName, String hypervisor, String os, int cpu, int speed, long memory, Long diskSize, List computeTags, List rootDiskTags, Map> networkNicMap, DeploymentPlan plan, - Map> extraDhcpOptionMap, Long diskOfferingId) + Map> extraDhcpOptionMap, Long diskOfferingId, Long rootDiskKmsKeyId, List dataDiskInfoList, Volume volume, Snapshot snapshot) throws InsufficientCapacityException { // VirtualMachineEntityImpl vmEntity = new VirtualMachineEntityImpl(id, owner, hostName, displayName, cpu, speed, memory, computeTags, rootDiskTags, networks, vmEntityManager); @@ -276,9 +294,11 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow ServiceOfferingVO computeOffering = _serviceOfferingDao.findById(vm.getId(), vm.getServiceOfferingId()); + VMTemplateVO iso = _templateDao.findByIdIncludingRemoved(Long.valueOf(isoId)); + DiskOfferingInfo rootDiskOfferingInfo = new DiskOfferingInfo(); - if (diskOfferingId == null) { + if (diskOfferingId == null && !_itMgr.isBlankInstance(iso)) { throw new InvalidParameterValueException("Installing from ISO requires a disk offering to be specified for the root disk."); } DiskOfferingVO diskOffering = _diskOfferingDao.findById(diskOfferingId); @@ -298,9 +318,10 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow rootDiskOfferingInfo.setDiskOffering(diskOffering); rootDiskOfferingInfo.setSize(size); + rootDiskOfferingInfo.setKmsKeyId(rootDiskKmsKeyId); if (diskOffering.isCustomizedIops() != null && diskOffering.isCustomizedIops()) { - Map userVmDetails = _userVmDetailsDao.listDetailsKeyPairs(vm.getId()); + Map userVmDetails = _vmInstanceDetailsDao.listDetailsKeyPairs(vm.getId()); if (userVmDetails != null) { String minIops = userVmDetails.get("minIopsDo"); @@ -310,6 +331,14 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow rootDiskOfferingInfo.setMaxIops(maxIops != null && maxIops.trim().length() > 0 ? Long.parseLong(maxIops) : null); } } + List dataDiskOfferings = new ArrayList<>(); + List dataDiskDeviceIds = new ArrayList<>(); + if (dataDiskInfoList != null) { + dataDiskOfferings.addAll(dataDiskInfoList); + for (VmDiskInfo dataDiskInfo : dataDiskInfoList) { + dataDiskDeviceIds.add(dataDiskInfo.getDeviceId()); + } + } LinkedHashMap> networkIpMap = new LinkedHashMap>(); for (String uuid : networkNicMap.keySet()) { @@ -321,7 +350,8 @@ public VirtualMachineEntity createVirtualMachineFromScratch(String id, String ow HypervisorType hypervisorType = HypervisorType.valueOf(hypervisor); - _itMgr.allocate(vm.getInstanceName(), _templateDao.findById(new Long(isoId)), computeOffering, rootDiskOfferingInfo, new ArrayList(), networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null); + _itMgr.allocate(vm.getInstanceName(), iso, computeOffering, rootDiskOfferingInfo, dataDiskOfferings, dataDiskDeviceIds, + networkIpMap, plan, hypervisorType, extraDhcpOptionMap, null, volume, snapshot); return vmEntity; } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java index 5a8dc3038aa8..1441a9957772 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java @@ -28,9 +28,13 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import javax.inject.Inject; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.InternalBackupJoinVO; +import org.apache.cloudstack.backup.dao.InternalBackupJoinDao; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; @@ -42,6 +46,7 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.storage.ImageStoreService; +import org.apache.cloudstack.storage.backup.BackupObject; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; @@ -89,12 +94,15 @@ public class DataMigrationUtility { HostDao hostDao; @Inject SnapshotDao snapshotDao; + + @Inject + InternalBackupJoinDao internalBackupJoinDao; /** * This function verifies if the given image store contains data objects that are not in any of the following states: * "Ready" "Allocated", "Destroying", "Destroyed", "Failed". If this is the case, and if the migration policy is complete, * the migration is terminated. */ - public boolean filesReadyToMigrate(Long srcDataStoreId, List templates, List snapshots, List volumes) { + public boolean filesReadyToMigrate(Long srcDataStoreId, List templates, List snapshots, List volumes, List backups) { State[] validStates = {State.Ready, State.Allocated, State.Destroying, State.Destroyed, State.Failed}; boolean isReady = true; for (TemplateDataStoreVO template : templates) { @@ -109,14 +117,48 @@ public boolean filesReadyToMigrate(Long srcDataStoreId, List backups) { + List invalidBackupStates = Arrays.asList(Backup.Status.BackingUp, Backup.Status.Restoring); + List invalidBackupCompressionStatus = Arrays.asList(Backup.CompressionStatus.Compressing, Backup.CompressionStatus.FinalizingCompression); + + List> backupChains; + Set backupIdsAlreadyInChain = new HashSet<>(); + + for (InternalBackupJoinVO backup : backups) { + if (backup.getStatus() == Backup.Status.BackedUp && !backupIdsAlreadyInChain.contains(backup.getId())) { + backupChains = createBackupChain(backup); + backupChains.forEach(list -> backupIdsAlreadyInChain.add(list.stream().map(BackupObject::getId).findFirst().get())); + + for (List backupVolumeChain : backupChains) { + BackupObject backupObject = backupVolumeChain.get(0); + + if (invalidBackupStates.contains(backupObject.getStatus())) { + logger.debug("Migration is not possible because backup {} is in {} state.", backupObject.getUuid(), backupObject.getStatus()); + return false; + } + + if (invalidBackupCompressionStatus.contains(backupObject.getCompressionStatus())) { + logger.debug("Migration is not possible because backup {} is currently being compressed. Current compression status: {}.", backupObject.getUuid(), backupObject.getCompressionStatus()); + return false; + } + } + } + } + + return true; + } + private boolean filesReadyToMigrate(Long srcDataStoreId) { List templates = templateDataStoreDao.listByStoreId(srcDataStoreId); List snapshots = snapshotDataStoreDao.listByStoreId(srcDataStoreId, DataStoreRole.Image); List volumes = volumeDataStoreDao.listByStoreId(srcDataStoreId); - return filesReadyToMigrate(srcDataStoreId, templates, snapshots, volumes); + List backups = internalBackupJoinDao.listByImageStoreId(srcDataStoreId); + + return filesReadyToMigrate(srcDataStoreId, templates, snapshots, volumes, backups); } protected void checkIfCompleteMigrationPossible(ImageStoreService.MigrationPolicy policy, Long srcDataStoreId) { @@ -175,19 +217,58 @@ protected List getSortedValidSourcesList(DataStore srcDataStore, Map return files; } - protected List getSortedValidSourcesList(DataStore srcDataStore, Map, Long>> snapshotChains, - Map, Long>> childTemplates) { + Map, Long>> childTemplates, Map>, Long>> backupChains) { List files = new ArrayList<>(); files.addAll(getAllReadyTemplates(srcDataStore, childTemplates)); files.addAll(getAllReadySnapshotsAndChains(srcDataStore, snapshotChains)); files.addAll(getAllReadyVolumes(srcDataStore)); + files.addAll(getAllReadyBackupsAndChains(srcDataStore, backupChains)); files = sortFilesOnSize(files, snapshotChains); return files; } + protected List getAllReadyBackupsAndChains(DataStore srcDataStore, Map>, Long>> backupChains) { + List backups = internalBackupJoinDao.listByImageStoreId(srcDataStore.getId()); + return getAllReadyBackupsAndChains(backupChains, backups); + } + + private List getAllReadyBackupsAndChains(Map>, Long>> backupsChains, List backups) { + Set backupIdsToMigrate = backups.stream().map(InternalBackupJoinVO::getId).collect(Collectors.toSet()); + List> backupChains; + Set backupIdsAlreadyInChain = new HashSet<>(); + List files = new LinkedList<>(); + + for (InternalBackupJoinVO backup : backups) { + long backupId = backup.getId(); + + if (backup.getStatus() == Backup.Status.BackedUp && !backupIdsAlreadyInChain.contains(backupId)) { + backupChains = createBackupChain(backup); + backupChains.forEach(list -> backupIdsAlreadyInChain.add(list.stream().map(BackupObject::getId).findFirst().get())); + BackupObject parent = backupChains.get(0).get(0); + files.add(parent); + backupsChains.put(parent, new Pair<>(backupChains, backupChains.stream().map(list -> getTotalChainSize(list.stream() + .filter(back -> backupIdsToMigrate.contains(parent.getId())).collect(Collectors.toList())) + ).reduce(Long::sum).get())); + } + } + + return (List) (List) files; + } + + private List> createBackupChain(InternalBackupJoinVO backup) { + List> chain = new LinkedList<>(); + BackupObject backupObject = BackupObject.getBackupObject(backup); + + chain.addAll(backupObject.getParents(backup.getParentId())); + chain.add(internalBackupJoinDao.listById(backup.getId()).stream().map(BackupObject::getBackupObject).collect(Collectors.toList())); + chain.addAll(backupObject.getChildren()); + + return chain; + } + protected List sortFilesOnSize(List files, Map, Long>> snapshotChains) { Collections.sort(files, new Comparator() { @Override 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 f7aa957abb94..84a397349cec 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestrator.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.engine.orchestration; +import static com.cloud.configuration.ConfigurationManager.MESSAGE_DELETE_VLAN_IP_RANGE_EVENT; + import java.net.URI; import java.util.ArrayList; import java.util.Arrays; @@ -39,14 +41,6 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.configuration.Resource; -import com.cloud.dc.ASNumberVO; -import com.cloud.bgp.BGPService; -import com.cloud.dc.VlanDetailsVO; -import com.cloud.dc.dao.ASNumberDao; -import com.cloud.dc.dao.VlanDetailsDao; -import com.cloud.network.dao.NsxProviderDao; -import com.cloud.resourcelimit.CheckedReservation; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.annotation.AnnotationService; import org.apache.cloudstack.annotation.dao.AnnotationDao; @@ -69,6 +63,7 @@ import org.apache.commons.lang3.BooleanUtils; import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; @@ -90,8 +85,11 @@ import com.cloud.alert.AlertManager; import com.cloud.api.query.dao.DomainRouterJoinDao; import com.cloud.api.query.vo.DomainRouterJoinVO; +import com.cloud.bgp.BGPService; import com.cloud.configuration.ConfigurationManager; +import com.cloud.configuration.Resource; import com.cloud.configuration.Resource.ResourceType; +import com.cloud.dc.ASNumberVO; import com.cloud.dc.ClusterVO; import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenter.NetworkType; @@ -99,12 +97,15 @@ import com.cloud.dc.DataCenterVnetVO; import com.cloud.dc.PodVlanMapVO; import com.cloud.dc.Vlan; +import com.cloud.dc.VlanDetailsVO; import com.cloud.dc.VlanVO; +import com.cloud.dc.dao.ASNumberDao; import com.cloud.dc.dao.ClusterDao; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.DataCenterVnetDao; import com.cloud.dc.dao.PodVlanMapDao; import com.cloud.dc.dao.VlanDao; +import com.cloud.dc.dao.VlanDetailsDao; import com.cloud.deploy.DataCenterDeployment; import com.cloud.deploy.DeployDestination; import com.cloud.deploy.DeploymentPlan; @@ -155,6 +156,8 @@ import com.cloud.network.dao.FirewallRulesDao; import com.cloud.network.dao.IPAddressDao; import com.cloud.network.dao.IPAddressVO; +import com.cloud.network.dao.Ipv6GuestPrefixSubnetNetworkMapDao; +import com.cloud.network.dao.NetrisProviderDao; import com.cloud.network.dao.NetworkAccountDao; import com.cloud.network.dao.NetworkAccountVO; import com.cloud.network.dao.NetworkDao; @@ -165,6 +168,7 @@ import com.cloud.network.dao.NetworkServiceMapDao; import com.cloud.network.dao.NetworkServiceMapVO; import com.cloud.network.dao.NetworkVO; +import com.cloud.network.dao.NsxProviderDao; import com.cloud.network.dao.PhysicalNetworkDao; import com.cloud.network.dao.PhysicalNetworkServiceProviderDao; import com.cloud.network.dao.PhysicalNetworkTrafficTypeDao; @@ -173,6 +177,10 @@ import com.cloud.network.dao.RemoteAccessVpnDao; import com.cloud.network.dao.RemoteAccessVpnVO; import com.cloud.network.dao.RouterNetworkDao; +import org.apache.cloudstack.extension.Extension; +import org.apache.cloudstack.extension.ExtensionHelper; +import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement; + import com.cloud.network.element.AggregatedCommandExecutor; import com.cloud.network.element.ConfigDriveNetworkElement; import com.cloud.network.element.DhcpServiceProvider; @@ -212,6 +220,7 @@ import com.cloud.offerings.dao.NetworkOfferingDetailsDao; import com.cloud.offerings.dao.NetworkOfferingServiceMapDao; import com.cloud.resource.ResourceManager; +import com.cloud.resourcelimit.CheckedReservation; import com.cloud.server.ManagementServer; import com.cloud.user.Account; import com.cloud.user.ResourceLimitService; @@ -252,8 +261,8 @@ import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachine.Type; +import com.cloud.vm.VirtualMachineManager; import com.cloud.vm.VirtualMachineProfile; import com.cloud.vm.dao.DomainRouterDao; import com.cloud.vm.dao.NicDao; @@ -265,9 +274,6 @@ import com.cloud.vm.dao.UserVmDao; import com.cloud.vm.dao.VMInstanceDao; import com.googlecode.ipv6.IPv6Address; -import org.jetbrains.annotations.NotNull; - -import static com.cloud.configuration.ConfigurationManager.MESSAGE_DELETE_VLAN_IP_RANGE_EVENT; /** * NetworkManagerImpl implements NetworkManager. @@ -311,6 +317,8 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject NetworkOfferingDetailsDao _ntwkOffDetailsDao; @Inject + NetworkOfferingServiceMapDao networkOfferingServiceMapDao; + @Inject AccountGuestVlanMapDao _accountGuestVlanMapDao; @Inject DataCenterVnetDao _datacenterVnetDao; @@ -359,9 +367,17 @@ public class NetworkOrchestrator extends ManagerBase implements NetworkOrchestra @Inject private NsxProviderDao nsxProviderDao; @Inject + private NetrisProviderDao netrisProviderDao; + @Inject private ASNumberDao asNumberDao; @Inject private BGPService bgpService; + @Inject + private Ipv6GuestPrefixSubnetNetworkMapDao ipv6GuestPrefixSubnetNetworkMapDao; + @Inject + protected ExtensionHelper extensionHelper; + @Inject + private NetworkExtensionElement networkExtensionElement; @Override public List getNetworkGurus() { @@ -453,7 +469,29 @@ public void setDhcpProviders(final List dhcpProviders) { SearchBuilder AssignIpAddressSearch; SearchBuilder AssignIpAddressFromPodVlanSearch; - HashMap _lastNetworkIdsToFree = new HashMap(); + HashMap _lastNetworkIdsToFree = new HashMap<>(); + + /** + * Returns the full list of network elements to iterate when implementing, + * shutting down, or otherwise orchestrating a network. + * + *

The base list ({@link #networkElements}, wired by Spring) is extended + * at runtime with one transient {@link NetworkExtensionElement} per + * registered {@code NetworkOrchestrator} extension. This keeps the + * Spring bean list free from {@code NetworkExtensionElement} and allows + * dynamic discovery of extensions without a restart.

+ */ + protected List getNetworkElementsIncludingExtensions() { + List extensions = extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator); + if (CollectionUtils.isEmpty(extensions)) { + return networkElements; + } + List combined = new ArrayList<>(networkElements); + for (Extension ext : extensions) { + combined.add(networkExtensionElement.withProviderName(ext.getName())); + } + return combined; + } private void updateRouterDefaultDns(final VirtualMachineProfile vmProfile, final NicProfile nicProfile) { if (!Type.DomainRouter.equals(vmProfile.getType()) || !nicProfile.isDefaultNic()) { @@ -491,8 +529,8 @@ private void updateRouterDefaultDns(final VirtualMachineProfile vmProfile, final @DB public boolean configure(final String name, final Map params) throws ConfigurationException { // populate providers - final Map> defaultSharedNetworkOfferingProviders = new HashMap>(); - final Set defaultProviders = new HashSet(); + final Map> defaultSharedNetworkOfferingProviders = new HashMap<>(); + final Set defaultProviders = new HashSet<>(); final Set tungstenProvider = new HashSet<>(); defaultProviders.add(Network.Provider.VirtualRouter); @@ -511,11 +549,11 @@ public boolean configure(final String name, final Map params) th defaultIsolatedNetworkOfferingProviders.put(Service.PortForwarding, defaultProviders); defaultIsolatedNetworkOfferingProviders.put(Service.Vpn, defaultProviders); - final Map> defaultSharedSGEnabledNetworkOfferingProviders = new HashMap>(); + final Map> defaultSharedSGEnabledNetworkOfferingProviders = new HashMap<>(); defaultSharedSGEnabledNetworkOfferingProviders.put(Service.Dhcp, defaultProviders); defaultSharedSGEnabledNetworkOfferingProviders.put(Service.Dns, defaultProviders); defaultSharedSGEnabledNetworkOfferingProviders.put(Service.UserData, defaultProviders); - final Set sgProviders = new HashSet(); + final Set sgProviders = new HashSet<>(); sgProviders.add(Provider.SecurityGroupProvider); defaultSharedSGEnabledNetworkOfferingProviders.put(Service.SecurityGroup, sgProviders); @@ -528,7 +566,7 @@ public boolean configure(final String name, final Map params) th defaultTungstenSharedSGEnabledNetworkOfferingProviders.put(Service.SecurityGroup, tungstenProvider); - final Map> defaultIsolatedSourceNatEnabledNetworkOfferingProviders = new HashMap>(); + final Map> defaultIsolatedSourceNatEnabledNetworkOfferingProviders = new HashMap<>(); defaultProviders.clear(); defaultProviders.add(Network.Provider.VirtualRouter); defaultIsolatedSourceNatEnabledNetworkOfferingProviders.put(Service.Dhcp, defaultProviders); @@ -542,7 +580,7 @@ public boolean configure(final String name, final Map params) th defaultIsolatedSourceNatEnabledNetworkOfferingProviders.put(Service.PortForwarding, defaultProviders); defaultIsolatedSourceNatEnabledNetworkOfferingProviders.put(Service.Vpn, defaultProviders); - final Map> defaultVPCOffProviders = new HashMap>(); + final Map> defaultVPCOffProviders = new HashMap<>(); defaultProviders.clear(); defaultProviders.add(Network.Provider.VPCVirtualRouter); defaultVPCOffProviders.put(Service.Dhcp, defaultProviders); @@ -555,36 +593,37 @@ public boolean configure(final String name, final Map params) th defaultVPCOffProviders.put(Service.StaticNat, defaultProviders); defaultVPCOffProviders.put(Service.PortForwarding, defaultProviders); defaultVPCOffProviders.put(Service.Vpn, defaultProviders); + defaultVPCOffProviders.put(Service.Firewall, defaultProviders); Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(final TransactionStatus status) { - NetworkOfferingVO offering = null; + NetworkOfferingVO offering; //#1 - quick cloud network offering if (_networkOfferingDao.findByUniqueName(NetworkOffering.QuickCloudNoServices) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.QuickCloudNoServices, "Offering for QuickCloud with no services", TrafficType.Guest, null, true, - Availability.Optional, null, new HashMap>(), true, Network.GuestType.Shared, false, null, true, null, true, - false, null, false, null, true, false, false, false, null, null, null, true, null, null, false); + Availability.Optional, null, new HashMap<>(), true, Network.GuestType.Shared, false, null, true, null, true, + false, null, false, null, true, false, false, false, false, null, null, null, true, null, null, false); } //#2 - SG enabled network offering if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedNetworkOfferingWithSGService) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedNetworkOfferingWithSGService, "Offering for Shared Security group enabled networks", TrafficType.Guest, null, true, Availability.Optional, null, defaultSharedNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true, - null, true, false, null, false, null, true, false, false, false, null, null, null, true, null, null, false); + null, true, false, null, false, null, true, false, false, false, false, null, null, null, true, null, null, false); } //#3 - shared network offering with no SG service if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedNetworkOffering) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedNetworkOffering, "Offering for Shared networks", TrafficType.Guest, null, true, Availability.Optional, null, defaultSharedNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true, null, true, false, null, false, - null, true, false, false, false, null,null, null, true, null, null, false); + null, true, false, false, false, false, null, null, null, true, null, null, false); } if (_networkOfferingDao.findByUniqueName(NetworkOffering.DEFAULT_TUNGSTEN_SHARED_NETWORK_OFFERING_WITH_SGSERVICE) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DEFAULT_TUNGSTEN_SHARED_NETWORK_OFFERING_WITH_SGSERVICE, "Offering for Tungsten Shared Security group enabled networks", TrafficType.Guest, null, true, Availability.Optional, null, defaultTungstenSharedSGEnabledNetworkOfferingProviders, true, Network.GuestType.Shared, false, null, true, - null, true, false, null, false, null, true, false, true, false, null, null,null, true, null, null, false); + null, true, false, null, false, null, true, false, true, false, false, null, null, null, true, null, null, false); offering.setState(NetworkOffering.State.Enabled); _networkOfferingDao.update(offering.getId(), offering); } @@ -594,15 +633,14 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingWithSourceNatService, "Offering for Isolated networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Required, null, defaultIsolatedSourceNatEnabledNetworkOfferingProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, - true, false, false, false, null, null,null, true, null, null, false); + true, false, false, false, false, null, null, null, true, null, null, false); } //#5 - default vpc offering with LB service if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworks, "Offering for Isolated VPC networks with Source Nat service enabled", TrafficType.Guest, null, false, Availability.Optional, null, - defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false); - + defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, true, null, false, false, null, false, null, true, true, false, false, false, null, null, null, true, null, null, false); } //#6 - default vpc offering with no LB service @@ -611,22 +649,22 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { defaultVPCOffProviders.remove(Service.Lb); offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksNoLB, "Offering for Isolated VPC networks with Source Nat service enabled and LB service disabled", TrafficType.Guest, null, false, Availability.Optional, - null, defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, null, null, null,true, null, null, false); + null, defaultVPCOffProviders, true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, false, null, null, null, true, null, null, false); } //#7 - isolated offering with source nat disabled if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOffering) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOffering, "Offering for Isolated networks with no Source Nat service", TrafficType.Guest, null, true, Availability.Optional, null, defaultIsolatedNetworkOfferingProviders, true, Network.GuestType.Isolated, false, null, - true, null, true, false, null, false, null, true, false, false, false, null, null, null, true, null, null, false); + true, null, true, false, null, false, null, true, false, false, false, false, null, null, null, true, null, null, false); } //#8 - network offering with internal lb service - final Map> internalLbOffProviders = new HashMap>(); - final Set defaultVpcProvider = new HashSet(); + final Map> internalLbOffProviders = new HashMap<>(); + final Set defaultVpcProvider = new HashSet<>(); defaultVpcProvider.add(Network.Provider.VPCVirtualRouter); - final Set defaultInternalLbProvider = new HashSet(); + final Set defaultInternalLbProvider = new HashSet<>(); defaultInternalLbProvider.add(Network.Provider.InternalLbVm); internalLbOffProviders.put(Service.Dhcp, defaultVpcProvider); @@ -640,18 +678,18 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultIsolatedNetworkOfferingForVpcNetworksWithInternalLB, "Offering for Isolated VPC networks with Internal Lb support", TrafficType.Guest, null, false, Availability.Optional, null, internalLbOffProviders, - true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, null, null, null, true, null, null, false); + true, Network.GuestType.Isolated, false, null, false, null, false, false, null, false, null, true, true, false, false, false, null, null, null, true, null, null, false); offering.setInternalLb(true); offering.setPublicLb(false); _networkOfferingDao.update(offering.getId(), offering); } - final Map> netscalerServiceProviders = new HashMap>(); - final Set vrProvider = new HashSet(); + final Map> netscalerServiceProviders = new HashMap<>(); + final Set vrProvider = new HashSet<>(); vrProvider.add(Provider.VirtualRouter); - final Set sgProvider = new HashSet(); + final Set sgProvider = new HashSet<>(); sgProvider.add(Provider.SecurityGroupProvider); - final Set nsProvider = new HashSet(); + final Set nsProvider = new HashSet<>(); nsProvider.add(Provider.Netscaler); netscalerServiceProviders.put(Service.Dhcp, vrProvider); netscalerServiceProviders.put(Service.Dns, vrProvider); @@ -660,10 +698,10 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { netscalerServiceProviders.put(Service.StaticNat, nsProvider); netscalerServiceProviders.put(Service.Lb, nsProvider); - final Map> serviceCapabilityMap = new HashMap>(); - final Map elb = new HashMap(); + final Map> serviceCapabilityMap = new HashMap<>(); + final Map elb = new HashMap<>(); elb.put(Capability.ElasticLb, "true"); - final Map eip = new HashMap(); + final Map eip = new HashMap<>(); eip.put(Capability.ElasticIp, "true"); serviceCapabilityMap.put(Service.Lb, elb); serviceCapabilityMap.put(Service.StaticNat, eip); @@ -671,7 +709,7 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { if (_networkOfferingDao.findByUniqueName(NetworkOffering.DefaultSharedEIPandELBNetworkOffering) == null) { offering = _configMgr.createNetworkOffering(NetworkOffering.DefaultSharedEIPandELBNetworkOffering, "Offering for Shared networks with Elastic IP and Elastic LB capabilities", TrafficType.Guest, null, true, Availability.Optional, null, - netscalerServiceProviders, true, Network.GuestType.Shared, false, null, true, serviceCapabilityMap, true, false, null, false, null, true, false, false, false, null, null, null, true, null, null, false); + netscalerServiceProviders, true, Network.GuestType.Shared, false, null, true, serviceCapabilityMap, true, false, null, false, null, true, false, false, false, false, null, null, null, true, null, null, false); offering.setDedicatedLB(false); _networkOfferingDao.update(offering.getId(), offering); } @@ -726,11 +764,6 @@ public boolean start() { return true; } - @Override - public boolean stop() { - return true; - } - protected NetworkOrchestrator() { setStateMachine(); } @@ -772,11 +805,11 @@ public List setupNetwork(final Account owner, final NetworkOf } } - final List networks = new ArrayList(); + final List networks = new ArrayList<>(); long related = -1; - for (final NetworkGuru guru : networkGurus) { + for (final NetworkGuru guru : getDesignNetworkGurus(offering)) { final Network network = guru.design(offering, plan, predefined, name, vpcId, owner); if (network == null) { continue; @@ -828,6 +861,11 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { if (domainId != null && aclType == ACLType.Domain) { _networksDao.addDomainToNetwork(id, domainId, subdomainAccess == null || subdomainAccess); } + String ipv6Cidr = network.getIp6Cidr(); + String ipv6Gateway = network.getIp6Gateway(); + if (StringUtils.isNoneBlank(ipv6Cidr, ipv6Gateway)) { + ipv6Service.assignIpv6SubnetToNetwork(ipv6Cidr, networkPersisted.getId()); + } } }); guru.setup(network, relatedFile); @@ -847,6 +885,46 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } + private List getDesignNetworkGurus(final NetworkOffering offering) { + if (!isIsolationMethodNetworkExtension(offering.getId())) { + return networkGurus; + } + + List extensionGurus = networkGurus.stream() + .filter(guru -> ExtensionHelper.NETWORK_EXTENSION_GURU_NAME.equals(guru.getName())) + .collect(Collectors.toList()); + if (CollectionUtils.isNotEmpty(extensionGurus)) { + return extensionGurus; + } + + logger.warn("Network offering {} requests {}={}, but {} is not registered; using default guru ordering", + offering.getUuid(), ExtensionHelper.NETWORK_ISOLATION_METHOD_DETAIL_KEY, + ExtensionHelper.NETWORK_EXTENSION_ISOLATION_METHOD, ExtensionHelper.NETWORK_EXTENSION_GURU_NAME); + return networkGurus; + } + + @Override + public boolean isIsolationMethodNetworkExtension(final Long networkOfferingId) { + if (networkOfferingId == null) { + return false; + } + + List providers = networkOfferingServiceMapDao.getDistinctProviders(networkOfferingId); + if (CollectionUtils.isEmpty(providers)) { + return false; + } + + for (String providerName : providers) { + if (!extensionHelper.isNetworkExtensionProvider(providerName)) { + continue; + } + if (extensionHelper.usesNetworkExtensionIsolation(providerName)) { + return true; + } + } + return false; + } + @NotNull private static NetworkVO getNetworkVO(long id, final NetworkOffering offering, final DeploymentPlan plan, final Network predefined, Network network, final NetworkGuru guru, final Account owner, @@ -858,6 +936,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; } @@ -890,7 +969,7 @@ public void allocate(final VirtualMachineProfile vm, final LinkedHashMap> profilesList = getOrderedNetworkNicProfileMapping(networks); - final List nics = new ArrayList(size); + final List nics = new ArrayList<>(size); NicProfile defaultNic = null; Network nextNetwork = null; for (Pair networkNicPair : profilesList) { @@ -967,16 +1046,16 @@ private List> getOrderedNetworkNicProfileMapping(final for (final Map.Entry> network : networks.entrySet()) { List requestedProfiles = network.getValue(); if (requestedProfiles == null) { - requestedProfiles = new ArrayList(); + requestedProfiles = new ArrayList<>(); } if (requestedProfiles.isEmpty()) { requestedProfiles.add(null); } for (final NicProfile requested : requestedProfiles) { - profilesList.add(new Pair(network.getKey(), requested)); + profilesList.add(new Pair<>(network.getKey(), requested)); } } - profilesList.sort(new Comparator>() { + profilesList.sort(new Comparator<>() { @Override public int compare(Pair pair1, Pair pair2) { int profile1Order = Integer.MAX_VALUE; @@ -1059,10 +1138,10 @@ public void saveExtraDhcpOptions(final String networkUuid, final Long nicId, fin } private NicVO persistNicAfterRaceCheck(final NicVO nic, final Long networkId, final NicProfile profile, int deviceId) { - return Transaction.execute(new TransactionCallback() { + return Transaction.execute(new TransactionCallback<>() { @Override public NicVO doInTransaction(TransactionStatus status) { - NicVO vo = _nicDao.findByIp4AddressAndNetworkId(profile.getIPv4Address(), networkId); + NicVO vo = _nicDao.findNonPlaceHolderByIp4AddressAndNetworkId(profile.getIPv4Address(), networkId); if (vo == null) { applyProfileToNic(nic, profile, deviceId); vo = _nicDao.persist(nic); @@ -1080,7 +1159,7 @@ private NicVO checkForRaceAndAllocateNic(final NicProfile requested, final Netwo logger.debug("Allocating NIC for Instance {} in Network {} with requested profile {}", vm.getVirtualMachine(), network, requested); final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, ntwkVO.getGuruName()); - NicVO vo = null; + NicVO vo; boolean retryIpAllocation; do { retryIpAllocation = false; @@ -1089,10 +1168,14 @@ private NicVO checkForRaceAndAllocateNic(final NicProfile requested, final Netwo return null; } - if (isNicAllocatedForNsxPublicNetworkOnVR(network, profile, vm)) { + if (isNicAllocatedForProviderPublicNetworkOnVR(network, profile, vm, Provider.Nsx)) { String guruName = "NsxPublicNetworkGuru"; NetworkGuru nsxGuru = AdapterBase.getAdapterByName(networkGurus, guruName); nsxGuru.allocate(network, profile, vm); + } else if (isNicAllocatedForProviderPublicNetworkOnVR(network, profile, vm, Provider.Netris)) { + String guruName = "NetrisPublicNetworkGuru"; + NetworkGuru netrisGuru = AdapterBase.getAdapterByName(networkGurus, guruName); + netrisGuru.allocate(network, profile, vm); } if (isDefaultNic != null) { @@ -1152,10 +1235,10 @@ public Pair allocateNic(final NicProfile requested, final N _nicDao.update(vo.getId(), vo); setMtuInVRNicProfile(networks, network.getTrafficType(), vmNic); } - return new Pair(vmNic, Integer.valueOf(deviceId)); + return new Pair<>(vmNic, Integer.valueOf(deviceId)); } - private boolean isNicAllocatedForNsxPublicNetworkOnVR(Network network, NicProfile requested, VirtualMachineProfile vm) { + private boolean isNicAllocatedForProviderPublicNetworkOnVR(Network network, NicProfile requested, VirtualMachineProfile vm, Provider provider) { if (ObjectUtils.anyNull(network, requested, vm)) { return false; } @@ -1165,7 +1248,9 @@ private boolean isNicAllocatedForNsxPublicNetworkOnVR(Network network, NicProfil return false; } long dataCenterId = vm.getVirtualMachine().getDataCenterId(); - if (nsxProviderDao.findByZoneId(dataCenterId) == null) { + if (Provider.Nsx == provider && nsxProviderDao.findByZoneId(dataCenterId) == null) { + return false; + } else if (Provider.Netris == provider && netrisProviderDao.findByZoneId(dataCenterId) == null) { return false; } @@ -1175,14 +1260,16 @@ private boolean isNicAllocatedForNsxPublicNetworkOnVR(Network network, NicProfil if (CollectionUtils.isEmpty(ips)) { return false; } + ips = ips.stream().filter(x -> !x.getAddress().addr().equals(requested.getIPv4Address())).collect(Collectors.toList()); IPAddressVO ip = ips.get(0); - VlanDetailsVO vlanDetail = vlanDetailsDao.findDetail(ip.getVlanId(), ApiConstants.NSX_DETAIL_KEY); + String detailKey = Provider.Nsx == provider ? ApiConstants.NSX_DETAIL_KEY : ApiConstants.NETRIS_DETAIL_KEY; + VlanDetailsVO vlanDetail = vlanDetailsDao.findDetail(ip.getVlanId(), detailKey); if (vlanDetail == null) { return false; } - boolean isForNsx = vlanDetail.getValue().equalsIgnoreCase("true"); - return isForNsx && !ip.isForSystemVms(); + boolean isForProvider = vlanDetail.getValue().equalsIgnoreCase("true"); + return isForProvider && !ip.isForSystemVms(); } private void setMtuDetailsInVRNic(final Pair networks, Network network, NicVO vo) { @@ -1430,7 +1517,7 @@ boolean isNetworkImplemented(final NetworkVO network) { Pair implementNetwork(final long networkId, final DeployDestination dest, final ReservationContext context, final boolean isRouter) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - Pair implemented = null; + Pair implemented; if (!isRouter) { implemented = implementNetwork(networkId, dest, context); } else { @@ -1441,7 +1528,7 @@ Pair implementNetwork(final long networkId, final Deploy // in issues. In order to avoid it, implementNetwork() call for VR is replaced with below code. final NetworkVO network = _networksDao.findById(networkId); final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); - implemented = new Pair(guru, network); + implemented = new Pair<>(guru, network); } return implemented; } @@ -1529,7 +1616,7 @@ private boolean networkMeetsPersistenceCriteria(NetworkVO network, NetworkOfferi @DB public Pair implementNetwork(final long networkId, final DeployDestination dest, final ReservationContext context) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - final Pair implemented = new Pair(null, null); + final Pair implemented = new Pair<>(null, null); NetworkVO network = _networksDao.findById(networkId); final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); @@ -1579,6 +1666,9 @@ public Pair implementNetwork(final long networkId, final // implement network elements and re-apply all the network rules implementNetworkElementsAndResources(dest, context, network, offering); + // reload network after implementing the network + network = _networksDao.findById(networkId); + long dcId = dest.getDataCenter().getId(); if (networkMeetsPersistenceCriteria(network, offering, false)) { setupPersistentNetwork(network, offering, dcId); @@ -1596,10 +1686,10 @@ public Pair implementNetwork(final long networkId, final return implemented; } catch (final NoTransitionException e) { logger.error(e.getMessage()); - return new Pair(null, null); + return new Pair<>(null, null); } catch (final CloudRuntimeException | OperationTimedoutException e) { logger.error("Caught exception: {}", e.getMessage()); - return new Pair(null, null); + return new Pair<>(null, null); } finally { if (implemented.first() == null) { logger.debug("Cleaning up because we're unable to implement the network {}", network); @@ -1643,7 +1733,7 @@ public void implementNetworkElementsAndResources(final DeployDestination dest, f && (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.SourceNat) || _networkModel.areServicesSupportedInNetwork(network.getId(), Service.Gateway)) && (network.getGuestType() == Network.GuestType.Isolated || network.getGuestType() == Network.GuestType.Shared && zone.getNetworkType() == NetworkType.Advanced)) { - List ips = null; + List ips; final Account owner = _entityMgr.findById(Account.class, network.getAccountId()); if (network.getVpcId() != null) { ips = _ipAddressDao.listByAssociatedVpc(network.getVpcId(), true); @@ -1672,7 +1762,8 @@ public void implementNetworkElementsAndResources(final DeployDestination dest, f } } - for (final NetworkElement element : networkElements) { + List allNetworkElements = getNetworkElementsIncludingExtensions(); + for (final NetworkElement element : allNetworkElements) { if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) { ((AggregatedCommandExecutor) element).prepareAggregatedExecution(network, dest); } @@ -1689,7 +1780,7 @@ public void implementNetworkElementsAndResources(final DeployDestination dest, f ex.addProxyObject(_entityMgr.findById(DataCenter.class, network.getDataCenterId()).getUuid()); throw ex; } - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : allNetworkElements) { if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) { if (!((AggregatedCommandExecutor) element).completeAggregatedExecution(network, dest)) { logger.warn("Failed to re-program the network as a part of network {} implement due to aggregated commands execution failure!", network); @@ -1701,8 +1792,9 @@ public void implementNetworkElementsAndResources(final DeployDestination dest, f } } } + reconfigureAndApplyStaticRouteForVpcVpn(network); } finally { - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : allNetworkElements) { if (element instanceof AggregatedCommandExecutor && providersToImplement.contains(element.getProvider())) { ((AggregatedCommandExecutor) element).cleanupAggregatedExecution(network, dest); } @@ -1710,9 +1802,20 @@ public void implementNetworkElementsAndResources(final DeployDestination dest, f } } + private void reconfigureAndApplyStaticRouteForVpcVpn(Network network) { + if (network.getVpcId() != null) { + _vpcMgr.reconfigStaticNatForVpcVr(network.getVpcId()); + try { + _vpcMgr.applyStaticRouteForVpcVpnIfNeeded(network.getVpcId(), true); + } catch (ResourceUnavailableException e) { + logger.error("Unable to apply static routes for vpc " + network.getVpcId() + " due to " + e.getMessage()); + } + } + } + private void implementNetworkElements(final DeployDestination dest, final ReservationContext context, final Network network, final NetworkOffering offering, final List providersToImplement) throws ConcurrentOperationException, ResourceUnavailableException, InsufficientCapacityException { - for (NetworkElement element : networkElements) { + for (NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { // The physicalNetworkId will not get translated into a uuid by the response serializer, @@ -1946,6 +2049,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { ip.setOneToOneNat(false); ip.setAssociatedWithVmId(null); ip.setVmIp(null); + ip.setForRouter(false); _ipAddressDao.update(ip.getId(), ip); } } @@ -2004,7 +2108,7 @@ public void doInTransactionWithoutResult(TransactionStatus status) { @Override public void configureUpdateInSequence(Network network) { List providers = getNetworkProviders(network.getId()); - for (NetworkElement element : networkElements) { + for (NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providers.contains(element.getProvider())) { if (element instanceof RedundantResource) { ((RedundantResource) element).configureResource(network); @@ -2017,7 +2121,7 @@ public void configureUpdateInSequence(Network network) { public int getResourceCount(Network network) { List providers = getNetworkProviders(network.getId()); int resourceCount = 0; - for (NetworkElement element : networkElements) { + for (NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providers.contains(element.getProvider())) { //currently only one element implements the redundant resource interface if (element instanceof RedundantResource) { @@ -2048,7 +2152,7 @@ public void configureExtraDhcpOptions(Network network, long nicId) { @Override public void finalizeUpdateInSequence(Network network, boolean success) { List providers = getNetworkProviders(network.getId()); - for (NetworkElement element : networkElements) { + for (NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providers.contains(element.getProvider())) { //currently only one element implements the redundant resource interface if (element instanceof RedundantResource) { @@ -2075,7 +2179,7 @@ public void setHypervisorHostname(VirtualMachineProfile vm, DeployDestination de } private void setHypervisorHostnameInNetwork(VirtualMachineProfile vm, DeployDestination dest, Network network, NicProfile profile, boolean migrationSuccessful) { - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.UserData) && element instanceof UserDataServiceProvider && (element instanceof ConfigDriveNetworkElement && !migrationSuccessful || element instanceof VirtualRouterElement && migrationSuccessful)) { String errorMsg = String.format("Failed to add hypervisor host name while applying the userdata during the migration of VM %s, " + @@ -2119,7 +2223,7 @@ public void prepare(final VirtualMachineProfile vmProfile, final DeployDestinati // we have to implement default nics first - to ensure that default network elements start up first in multiple //nics case // (need for setting DNS on Dhcp to domR's Ip4 address) - Collections.sort(nics, new Comparator() { + Collections.sort(nics, new Comparator<>() { @Override public int compare(final NicVO nic1, final NicVO nic2) { @@ -2157,7 +2261,7 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); final NicVO nic = _nicDao.findById(nicId); - NicProfile profile = null; + NicProfile profile; if (nic.getReservationStrategy() == Nic.ReservationStrategy.Start) { nic.setState(Nic.State.Reserving); nic.setReservationId(context.getReservationId()); @@ -2203,7 +2307,7 @@ public NicProfile prepareNic(final VirtualMachineProfile vmProfile, final Deploy updateNic(nic, network, 1); final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " @@ -2258,7 +2362,7 @@ public void prepareNicForMigration(final VirtualMachineProfile vm, final DeployD } final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " @@ -2302,10 +2406,11 @@ public void prepareAllNicsForMigration(final VirtualMachineProfile vm, final Dep } } final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { - throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s", element.getProvider().getName(), _physicalNetworkDao.findById(network.getPhysicalNetworkId()))); + throw new CloudRuntimeException(String.format("Service provider %s either doesn't exist or is not enabled in physical network: %s", + element.getProvider().getName(), _physicalNetworkDao.findById(network.getPhysicalNetworkId()))); } if (element instanceof NetworkMigrationResponder) { if (!((NetworkMigrationResponder) element).prepareMigration(profile, network, vm, dest, context)) { @@ -2318,7 +2423,7 @@ public void prepareAllNicsForMigration(final VirtualMachineProfile vm, final Dep vm.addNic(profile); } - final List addedURIs = new ArrayList(); + final List addedURIs = new ArrayList<>(); if (guestNetworkId != null) { final List publicIps = _ipAddressDao.listByAssociatedNetwork(guestNetworkId, null); for (final IPAddressVO userIp : publicIps) { @@ -2383,7 +2488,7 @@ public void commitNicForMigration(final VirtualMachineProfile src, final Virtual } final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " @@ -2419,7 +2524,7 @@ public void rollbackNicForMigration(final VirtualMachineProfile src, final Virtu } final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " @@ -2506,7 +2611,7 @@ public Pair doInTransaction(final TransactionStatus status) final Network network = networkToRelease.first(); final NicProfile profile = networkToRelease.second(); final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { logger.debug("Asking {} to release {}", element.getName(), profile); //NOTE: Context appear to never be used in release method @@ -2569,7 +2674,7 @@ protected void removeNic(final VirtualMachineProfile vm, final NicVO nic) { */ if (nic.getReservationStrategy() == Nic.ReservationStrategy.Create) { final List providersToImplement = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToImplement.contains(element.getProvider())) { logger.debug("Asking {} to release {}, according to the reservation strategy {}.", element.getName(), nic, nic.getReservationStrategy()); try { @@ -2612,6 +2717,10 @@ && isDhcpAccrossMultipleSubnetsSupported(dhcpServiceProvider)) { final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, network.getGuruName()); guru.deallocate(network, profile, vm); + if (nic.getReservationStrategy() == Nic.ReservationStrategy.Create) { + applyProfileToNicForRelease(nic, profile); + _nicDao.update(nic.getId(), nic); + } if (BooleanUtils.isNotTrue(preserveNics)) { _nicDao.remove(nic.getId()); } @@ -2693,7 +2802,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 @@ -2704,10 +2813,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 @@ -2716,7 +2840,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); @@ -2958,19 +3083,19 @@ private Network createGuestNetwork(final long networkOfferingId, final String na throw new InvalidParameterValueException("Virtual Guest Cidr " + cidr + " is not RFC 1918 or 6598 compliant"); } - final String networkDomainFinal = networkDomain; - final String vlanIdFinal = vlanId; - final Boolean subdomainAccessFinal = subdomainAccess; - final Network network = Transaction.execute(new TransactionCallback() { - @Override - public Network doInTransaction(final TransactionStatus status) { - Long physicalNetworkId = null; - if (pNtwk != null) { - physicalNetworkId = pNtwk.getId(); - } - final DataCenterDeployment plan = new DataCenterDeployment(zoneId, null, null, null, null, physicalNetworkId); - final NetworkVO userNetwork = new NetworkVO(); - userNetwork.setNetworkDomain(networkDomainFinal); + final String networkDomainFinal = networkDomain; + final String vlanIdFinal = vlanId; + final Boolean subdomainAccessFinal = subdomainAccess; + final Network network = Transaction.execute(new TransactionCallback<>() { + @Override + public Network doInTransaction(final TransactionStatus status) { + Long physicalNetworkId = null; + if (pNtwk != null) { + physicalNetworkId = pNtwk.getId(); + } + final DataCenterDeployment plan = new DataCenterDeployment(zoneId, null, null, null, null, physicalNetworkId); + final NetworkVO userNetwork = new NetworkVO(); + userNetwork.setNetworkDomain(networkDomainFinal); if (cidr != null && gateway != null) { userNetwork.setCidr(cidr); @@ -3042,60 +3167,61 @@ public Network doInTransaction(final TransactionStatus status) { vlanIdFinal, zone)); } - userNetwork.setBroadcastUri(uri); - if (!vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { - userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan); - } else { - userNetwork.setBroadcastDomainType(BroadcastDomainType.Native); - } + userNetwork.setBroadcastUri(uri); + if (!vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { + userNetwork.setBroadcastDomainType(BroadcastDomainType.Vlan); } else { - if (vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { - throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!"); - } - URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan, isolatedPvlanType.toString()); - if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) { - throw new InvalidParameterValueException(String.format( - "Network with primary vlan %s and secondary vlan %s type %s already exists or overlaps with other network pvlans in zone %s", - vlanIdFinal, isolatedPvlan, isolatedPvlanType, zone)); - } - userNetwork.setBroadcastUri(uri); - userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan); - userNetwork.setPvlanType(isolatedPvlanType); + userNetwork.setBroadcastDomainType(BroadcastDomainType.Native); } - } - userNetwork.setNetworkCidrSize(networkCidrSize); - final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, - isDisplayNetworkEnabled); - Network network = null; - if (networks == null || networks.isEmpty()) { - throw new CloudRuntimeException("Fail to create a network"); } else { - if (networks.size() > 0 && networks.get(0).getGuestType() == Network.GuestType.Isolated && networks.get(0).getTrafficType() == TrafficType.Guest) { - Network defaultGuestNetwork = networks.get(0); - for (final Network nw : networks) { - if (nw.getCidr() != null && nw.getCidr().equals(zone.getGuestNetworkCidr())) { - defaultGuestNetwork = nw; - } - } - network = defaultGuestNetwork; - } else { - // For shared network - network = networks.get(0); + if (vlanIdFinal.equalsIgnoreCase(Vlan.UNTAGGED)) { + throw new InvalidParameterValueException("Cannot support pvlan with untagged primary vlan!"); + } + URI uri = NetUtils.generateUriForPvlan(vlanIdFinal, isolatedPvlan, isolatedPvlanType.toString()); + if (_networksDao.listByPhysicalNetworkPvlan(physicalNetworkId, uri.toString(), isolatedPvlanType).size() > 0) { + throw new InvalidParameterValueException(String.format( + "Network with primary vlan %s and secondary vlan %s type %s already exists or overlaps with other network pvlans in zone %s", + vlanIdFinal, isolatedPvlan, isolatedPvlanType, zone)); } + userNetwork.setBroadcastUri(uri); + userNetwork.setBroadcastDomainType(BroadcastDomainType.Pvlan); + userNetwork.setPvlanType(isolatedPvlanType); } - - if (isResourceCountUpdateNeeded(ntwkOff)) { - changeAccountResourceCountOrRecalculateDomainResourceCount(owner.getAccountId(), domainId, isDisplayNetworkEnabled, true); + } + userNetwork.setNetworkCidrSize(networkCidrSize); + userNetwork.setKeepMacAddressOnPublicNic(keepMacAddressOnPublicNic); + final List networks = setupNetwork(owner, ntwkOff, userNetwork, plan, name, displayText, true, domainId, aclType, subdomainAccessFinal, vpcId, + isDisplayNetworkEnabled); + Network network; + if (networks == null || networks.isEmpty()) { + throw new CloudRuntimeException("Fail to create a network"); + } else { + if (networks.size() > 0 && networks.get(0).getGuestType() == Network.GuestType.Isolated && networks.get(0).getTrafficType() == TrafficType.Guest) { + Network defaultGuestNetwork = networks.get(0); + for (final Network nw : networks) { + if (nw.getCidr() != null && nw.getCidr().equals(zone.getGuestNetworkCidr())) { + defaultGuestNetwork = nw; + } + } + network = defaultGuestNetwork; + } else { + // For shared network + network = networks.get(0); } - UsageEventUtils.publishNetworkCreation(network); + } - return network; + if (isResourceCountUpdateNeeded(ntwkOff)) { + changeAccountResourceCountOrRecalculateDomainResourceCount(owner.getAccountId(), domainId, isDisplayNetworkEnabled, true); } - }); + UsageEventUtils.publishNetworkCreation(network); + + return network; + } + }); - CallContext.current().setEventDetails("Network Id: " + network.getId()); - CallContext.current().putContextParameter(Network.class, network.getUuid()); - return network; + CallContext.current().setEventDetails("Network ID: " + network.getUuid()); + CallContext.current().putContextParameter(Network.class, network.getUuid()); + return network; } } @@ -3196,10 +3322,10 @@ public boolean shutdownNetwork(final long networkId, final ReservationContext co final boolean success = shutdownNetworkElementsAndResources(context, cleanupElements, network); final NetworkVO networkFinal = network; - final boolean result = Transaction.execute(new TransactionCallback() { + final boolean result = Transaction.execute(new TransactionCallback<>() { @Override public Boolean doInTransaction(final TransactionStatus status) { - boolean result = false; + boolean result; if (success) { logger.debug("Network {} is shutdown successfully, cleaning up corresponding resources now.", networkFinal); @@ -3279,7 +3405,7 @@ public boolean shutdownNetworkElementsAndResources(final ReservationContext cont // 2) Shutdown all the network elements boolean success = true; - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToShutdown.contains(element.getProvider())) { try { logger.debug("Sending network shutdown to {}", element.getName()); @@ -3299,6 +3425,7 @@ public boolean shutdownNetworkElementsAndResources(final ReservationContext cont } } } + reconfigureAndApplyStaticRouteForVpcVpn(network); return success; } @@ -3389,7 +3516,7 @@ public boolean destroyNetwork(final long networkId, final ReservationContext con // get providers to destroy final List providersToDestroy = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (providersToDestroy.contains(element.getProvider())) { try { logger.debug("Sending destroy to {}", element); @@ -3416,7 +3543,7 @@ public boolean destroyNetwork(final long networkId, final ReservationContext con final NetworkVO networkFinal = network; try { - final List deletedVlanRangeToPublish = Transaction.execute(new TransactionCallback>() { + final List deletedVlanRangeToPublish = Transaction.execute(new TransactionCallback<>() { @Override public List doInTransaction(TransactionStatus status) { final NetworkGuru guru = AdapterBase.getAdapterByName(networkGurus, networkFinal.getGuruName()); @@ -3471,7 +3598,7 @@ public List doInTransaction(TransactionStatus status) { publishDeletedVlanRanges(deletedVlanRangeToPublish); if (_networksDao.findById(network.getId()) == null) { // remove its related ACL permission - final Pair, Long> networkMsg = new Pair, Long>(Network.class, networkFinal.getId()); + final Pair, Long> networkMsg = new Pair<>(Network.class, networkFinal.getId()); _messageBus.publish(_name, EntityManager.MESSAGE_REMOVE_ENTITY_EVENT, PublishScope.LOCAL, networkMsg); } UsageEventUtils.publishNetworkDeletion(network); @@ -3569,9 +3696,9 @@ protected void runInContext() { public void reallyRun() { try { - final List shutdownList = new ArrayList(); + final List shutdownList = new ArrayList<>(); final long currentTime = System.currentTimeMillis() / 1000; - final HashMap stillFree = new HashMap(); + final HashMap stillFree = new HashMap<>(); final List networkIds = _networksDao.findNetworksToGarbageCollect(); final int netGcWait = NetworkGcWait.value(); @@ -3760,7 +3887,7 @@ public boolean areRoutersRunning(final List routers) { public void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProfile, NicProfile nicProfile) { final List networkProviders = getNetworkProviders(network.getId()); - for (final NetworkElement element : networkElements) { + for (final NetworkElement element : getNetworkElementsIncludingExtensions()) { if (networkProviders.contains(element.getProvider())) { if (!_networkModel.isProviderEnabledInPhysicalNetwork(_networkModel.getPhysicalNetworkId(network), element.getProvider().getName())) { throw new CloudRuntimeException("Service provider " + element.getProvider().getName() + " either doesn't exist or is not enabled in physical network id: " @@ -3777,6 +3904,17 @@ public void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProf } } } + if (vmProfile.getType() == Type.User && element.getProvider() != null) { + if (_networkModel.areServicesSupportedInNetwork(network.getId(), Service.Dns) + && _networkModel.isProviderSupportServiceInNetwork(network.getId(), Service.Dns, element.getProvider()) && element instanceof DnsServiceProvider) { + final DnsServiceProvider sp = (DnsServiceProvider) element; + try { + sp.removeDnsEntry(network, nicProfile, vmProfile); + } catch (ResourceUnavailableException e) { + logger.error("Failed to remove dns entry due to: ", e); + } + } + } } } } @@ -3796,7 +3934,7 @@ public void cleanupNicDhcpDnsEntry(Network network, VirtualMachineProfile vmProf * @throws InsufficientCapacityException */ private boolean rollingRestartRouters(final NetworkVO network, final NetworkOffering offering, final DeployDestination dest, final ReservationContext context) throws ResourceUnavailableException, ConcurrentOperationException, InsufficientCapacityException { - if (!NetworkOrchestrationService.RollingRestartEnabled.value()) { + if (!isRollingRestartSupport(network)) { if (shutdownNetworkElementsAndResources(context, true, network)) { implementNetworkElementsAndResources(dest, context, network, offering); return true; @@ -3844,6 +3982,20 @@ private boolean rollingRestartRouters(final NetworkVO network, final NetworkOffe return areRoutersRunning(routerDao.findByNetwork(network.getId())); } + private boolean isRollingRestartSupport(final NetworkVO network) { + if (!NetworkOrchestrator.RollingRestartEnabled.value()) { + return false; + } + List services = _ntwkSrvcDao.getServicesInNetwork(network.getId()); + for (NetworkServiceMapVO service : services) { + NetworkElement element = _networkModel.getElementImplementingProvider(service.getProvider()); + if (element == null || !element.rollingRestartSupported()) { + return false; + } + } + return true; + } + private void setRestartRequired(final NetworkVO network, final boolean restartRequired) { logger.debug("Marking network {} with restartRequired={}", network, restartRequired); network.setRestartRequired(restartRequired); @@ -3942,7 +4094,7 @@ protected boolean isSharedNetworkOfferingWithServices(final long networkOffering @Override public List listVmNics(final long vmId, final Long nicId, final Long networkId, String keyword) { - List result = null; + List result; if (keyword == null || keyword.isEmpty()) { if (nicId == null && networkId == null) { @@ -3985,8 +4137,8 @@ public boolean reallocate(final VirtualMachineProfile vm, final DataCenterDeploy if (dc.getNetworkType() == NetworkType.Basic) { final List nics = _nicDao.listByVmId(vmInstance.getId()); final NetworkVO network = _networksDao.findById(nics.get(0).getNetworkId()); - final LinkedHashMap> profiles = new LinkedHashMap>(); - profiles.put(network, new ArrayList()); + final LinkedHashMap> profiles = new LinkedHashMap<>(); + profiles.put(network, new ArrayList<>()); Transaction.execute(new TransactionCallbackWithExceptionNoReturn() { @Override @@ -4120,7 +4272,7 @@ private boolean shutdownNetworkResources(final Network network, final Account ca // Mark all static rules as revoked and apply them on the backend (not in the DB) final List firewallStaticNatRules = _firewallDao.listByNetworkAndPurpose(network.getId(), Purpose.StaticNat); - final List staticNatRules = new ArrayList(); + final List staticNatRules = new ArrayList<>(); logger.debug("Releasing {} static nat rules for network {} as a part of shutdownNetworkRules", firewallStaticNatRules.size(), network); for (final FirewallRuleVO firewallStaticNatRule : firewallStaticNatRules) { @@ -4242,7 +4394,7 @@ private boolean shutdownNetworkResources(final Network network, final Account ca // Get all ip addresses, mark as releasing and release them on the backend final List userIps = _ipAddressDao.listByAssociatedNetwork(network.getId(), null); - final List publicIpsToRelease = new ArrayList(); + final List publicIpsToRelease = new ArrayList<>(); if (userIps != null && !userIps.isEmpty()) { for (final IPAddressVO userIp : userIps) { userIp.setState(IpAddress.State.Releasing); @@ -4283,7 +4435,7 @@ public void processHostAdded(long hostId) { @Override public void processConnect(final Host host, final StartupCommand cmd, final boolean forRebalance) throws ConnectionException { - if (!(cmd instanceof StartupRoutingCommand)) { + if (!(cmd instanceof StartupRoutingCommand) || cmd.isConnectionTransferred()) { return; } final long hostId = host.getId(); @@ -4291,7 +4443,7 @@ public void processConnect(final Host host, final StartupCommand cmd, final bool final String dataCenter = startup.getDataCenter(); - long dcId = -1; + long dcId; DataCenterVO dc = _dcDao.findByName(dataCenter); if (dc == null) { try { @@ -4308,7 +4460,7 @@ public void processConnect(final Host host, final StartupCommand cmd, final bool logger.debug("Host's hypervisorType is: {}", hypervisorType); - final List networkInfoList = new ArrayList(); + final List networkInfoList = new ArrayList<>(); // list all physicalnetworks in the zone & for each get the network names final List physicalNtwkList = _physicalNetworkDao.listByZone(dcId); @@ -4387,8 +4539,8 @@ public boolean processTimeout(final long agentId, final long seq) { @Override public Map finalizeServicesAndProvidersForNetwork(final NetworkOffering offering, final Long physicalNetworkId) { - final Map svcProviders = new HashMap(); - final Map> providerSvcs = new HashMap>(); + final Map svcProviders = new HashMap<>(); + final Map> providerSvcs = new HashMap<>(); final List servicesMap = _ntwkOfferingSrvcDao.listByNetworkOfferingId(offering.getId()); final boolean checkPhysicalNetwork = physicalNetworkId != null ? true : false; @@ -4405,6 +4557,12 @@ public Map finalizeServicesAndProvidersForNetwork(final NetworkO if (provider == null) { provider = _networkModel.getDefaultUniqueProviderForService(service).getName(); + } else { + final Provider resolvedProvider = _networkModel.resolveProvider(provider); + if (resolvedProvider == null) { + throw new InvalidParameterValueException("Invalid provider " + provider + " configured for service " + service); + } + provider = resolvedProvider.getName(); } // check that provider is supported @@ -4418,7 +4576,7 @@ public Map finalizeServicesAndProvidersForNetwork(final NetworkO svcProviders.put(service, provider); List l = providerSvcs.get(provider); if (l == null) { - providerSvcs.put(provider, l = new ArrayList()); + providerSvcs.put(provider, l = new ArrayList<>()); } l.add(service); } @@ -4428,9 +4586,12 @@ public Map finalizeServicesAndProvidersForNetwork(final NetworkO private List getNetworkProviders(final long networkId) { final List providerNames = _ntwkSrvcDao.getDistinctProviders(networkId); - final List providers = new ArrayList(); + final List providers = new ArrayList<>(); for (final String providerName : providerNames) { - providers.add(Network.Provider.getProvider(providerName)); + final Provider provider = _networkModel.resolveProvider(providerName); + if (provider != null) { + providers.add(provider); + } } return providers; @@ -4523,7 +4684,7 @@ private boolean getNicProfileDefaultNic(NicProfile nicProfile) { @Override public List getNicProfiles(final Long vmId, HypervisorType hypervisorType) { final List nics = _nicDao.listByVmId(vmId); - final List profiles = new ArrayList(); + final List profiles = new ArrayList<>(); if (nics != null) { for (final Nic nic : nics) { @@ -4589,14 +4750,14 @@ private void setStateMachine() { } private Map> getServiceProvidersMap(final long networkId) { - final Map> map = new HashMap>(); + final Map> map = new HashMap<>(); final List nsms = _ntwkSrvcDao.getServicesInNetwork(networkId); for (final NetworkServiceMapVO nsm : nsms) { Set providers = map.get(Service.getService(nsm.getService())); if (providers == null) { - providers = new HashSet(); + providers = new HashSet<>(); } - providers.add(Provider.getProvider(nsm.getProvider())); + providers.add(_networkModel.resolveProvider(nsm.getProvider())); map.put(Service.getService(nsm.getService()), providers); } return map; @@ -4606,14 +4767,14 @@ private Map> getServiceProvidersMap(final long networkId) public List getProvidersForServiceInNetwork(final Network network, final Service service) { final Map> service2ProviderMap = getServiceProvidersMap(network.getId()); if (service2ProviderMap.get(service) != null) { - final List providers = new ArrayList(service2ProviderMap.get(service)); + final List providers = new ArrayList<>(service2ProviderMap.get(service)); return providers; } return null; } protected List getElementForServiceInNetwork(final Network network, final Service service) { - final List elements = new ArrayList(); + final List elements = new ArrayList<>(); final List providers = getProvidersForServiceInNetwork(network, service); //Only support one provider now if (providers == null) { @@ -4647,7 +4808,7 @@ public LoadBalancingServiceProvider getLoadBalancingProviderForNetwork(final Net final List lbElements = getElementForServiceInNetwork(network, Service.Lb); NetworkElement lbElement = null; if (lbElements.size() > 1) { - String providerName = null; + String providerName; //get network offering details final NetworkOffering off = _entityMgr.findById(NetworkOffering.class, network.getNetworkOfferingId()); if (lbScheme == Scheme.Public) { @@ -4735,7 +4896,7 @@ public Pair importNic(final String macAddress, int deviceId } } final String finalSelectedIp = selectedIp; - final NicVO vo = Transaction.execute(new TransactionCallback() { + final NicVO vo = Transaction.execute(new TransactionCallback<>() { @Override public NicVO doInTransaction(TransactionStatus status) { if (StringUtils.isBlank(macAddress)) { @@ -4881,10 +5042,10 @@ public void unmanageNics(VirtualMachineProfile vm) { @Override public void expungeLbVmRefs(List vmIds, Long batchSize) { - if (CollectionUtils.isEmpty(networkElements) || CollectionUtils.isEmpty(vmIds)) { + if (CollectionUtils.isEmpty(vmIds)) { return; } - for (NetworkElement element : networkElements) { + for (NetworkElement element : getNetworkElementsIncludingExtensions()) { if (element instanceof LoadBalancingServiceProvider) { LoadBalancingServiceProvider lbProvider = (LoadBalancingServiceProvider)element; lbProvider.expungeLbVmRefs(vmIds, batchSize); @@ -4905,8 +5066,9 @@ public String getConfigComponentName() { @Override public ConfigKey[] getConfigKeys() { return new ConfigKey[]{NetworkGcWait, NetworkGcInterval, NetworkLockTimeout, DeniedRoutes, - GuestDomainSuffix, NetworkThrottlingRate, VmNetworkThrottlingRate, MinVRVersion, + GuestDomainSuffix, NetworkThrottlingRate, VmNetworkThrottlingRate, MinVRVersion, DhcpLeaseTimeout, PromiscuousMode, MacAddressChanges, ForgedTransmits, MacLearning, RollingRestartEnabled, - TUNGSTEN_ENABLED, NSX_ENABLED }; + TUNGSTEN_ENABLED, NSX_ENABLED, NETRIS_ENABLED, NETWORK_LB_HAPROXY_MAX_CONN, + NETWORK_LB_HAPROXY_IDLE_TIMEOUT}; } } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java index 933b4e0c5ce6..403e83bccf99 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java @@ -25,9 +25,13 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.Callable; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -36,10 +40,22 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.agent.AgentManager; +import com.cloud.agent.api.MigrateBackupsBetweenSecondaryStoragesCommand; +import com.cloud.agent.api.MigrateBetweenSecondaryStoragesCommandAnswer; import com.cloud.dc.dao.DataCenterDao; +import com.cloud.exception.AgentUnavailableException; +import com.cloud.exception.OperationTimedoutException; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.template.TemplateManager; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import org.apache.cloudstack.api.response.MigrationResponse; +import org.apache.cloudstack.backup.BackupDetailVO; +import org.apache.cloudstack.backup.dao.BackupDetailsDao; import org.apache.cloudstack.engine.orchestration.service.StorageOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -57,6 +73,7 @@ import org.apache.cloudstack.framework.config.Configurable; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.apache.cloudstack.storage.ImageStoreService.MigrationPolicy; +import org.apache.cloudstack.storage.backup.BackupObject; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; @@ -115,6 +132,12 @@ public class StorageOrchestrator extends ManagerBase implements StorageOrchestra TemplateDataFactory templateDataFactory; @Inject DataCenterDao dcDao; + @Inject + AgentManager agentManager; + @Inject + HostDao hostDao; + @Inject + BackupDetailsDao backupDetailDao; ConfigKey ImageStoreImbalanceThreshold = new ConfigKey<>("Advanced", Double.class, @@ -128,6 +151,7 @@ public class StorageOrchestrator extends ManagerBase implements StorageOrchestra private final Map zoneExecutorMap = new HashMap<>(); private final Map zonePendingWorkCountMap = new HashMap<>(); + private final Map zoneKvmIncrementalExecutorMap = new ConcurrentHashMap<>(); @Override public String getConfigComponentName() { @@ -171,7 +195,9 @@ public MigrationResponse migrateData(Long srcDataStoreId, List destDatasto DataStore srcDatastore = dataStoreManager.getDataStore(srcDataStoreId, DataStoreRole.Image); Map, Long>> snapshotChains = new HashMap<>(); Map, Long>> childTemplates = new HashMap<>(); - files = migrationHelper.getSortedValidSourcesList(srcDatastore, snapshotChains, childTemplates); + Map>, Long>> backupChains = new HashMap<>(); + files = migrationHelper.getSortedValidSourcesList(srcDatastore, snapshotChains, childTemplates, backupChains); + if (files.isEmpty()) { return new MigrationResponse(String.format("No files in Image store: %s to migrate", srcDatastore), migrationPolicy.toString(), true); @@ -227,7 +253,7 @@ public MigrationResponse migrateData(Long srcDataStoreId, List destDatasto } if (shouldMigrate(chosenFileForMigration, srcDatastore.getId(), destDatastoreId, storageCapacities, snapshotChains, childTemplates, migrationPolicy)) { - storageCapacities = migrateAway(chosenFileForMigration, storageCapacities, snapshotChains, childTemplates, srcDatastore, destDatastoreId, futures); + storageCapacities = migrateAway(chosenFileForMigration, storageCapacities, snapshotChains, childTemplates, backupChains, srcDatastore, destDatastoreId, futures); } else { if (migrationPolicy == MigrationPolicy.BALANCE) { continue; @@ -256,7 +282,7 @@ public MigrationResponse migrateResources(Long srcImgStoreId, Long destImgStoreI List templates = templateDataStoreDao.listByStoreIdAndTemplateIds(srcImgStoreId, templateIdList); List snapshots = snapshotDataStoreDao.listByStoreAndSnapshotIds(srcImgStoreId, DataStoreRole.Image, snapshotIdList); - if (!migrationHelper.filesReadyToMigrate(srcImgStoreId, templates, snapshots, Collections.emptyList())) { + if (!migrationHelper.filesReadyToMigrate(srcImgStoreId, templates, snapshots, Collections.emptyList(), Collections.emptyList())) { throw new CloudRuntimeException("Migration failed as there are data objects which are not Ready - i.e, they may be in Migrating, creating, copying, etc. states"); } files = migrationHelper.getSortedValidSourcesList(srcDatastore, snapshotChains, childTemplates, templates, snapshots); @@ -291,7 +317,7 @@ public MigrationResponse migrateResources(Long srcImgStoreId, Long destImgStoreI } if (storageCapacityBelowThreshold(storageCapacities, destImgStoreId)) { - storageCapacities = migrateAway(chosenFileForMigration, storageCapacities, snapshotChains, childTemplates, srcDatastore, destImgStoreId, futures); + storageCapacities = migrateAway(chosenFileForMigration, storageCapacities, snapshotChains, childTemplates, null, srcDatastore, destImgStoreId, futures); } else { message = "Migration failed. Destination store doesn't have enough capacity for migration"; success = false; @@ -355,15 +381,89 @@ protected Map> migrateAway( Map, Long>> snapshotChains, Map, Long>> templateChains, + Map>, Long>> backupChains, DataStore srcDatastore, Long destDatastoreId, List> futures) { Long fileSize = migrationHelper.getFileSize(chosenFileForMigration, snapshotChains, templateChains); storageCapacities = assumeMigrate(storageCapacities, srcDatastore.getId(), destDatastoreId, fileSize); + DataStore destDataStore = dataStoreManager.getDataStore(destDatastoreId, DataStoreRole.Image); + + boolean isKvmIncrementalBackup = backupChains != null && chosenFileForMigration instanceof BackupObject && backupChains.containsKey(chosenFileForMigration); + + if (isKvmIncrementalBackup) { + MigrateKvmIncrementalBackupTask task = new MigrateKvmIncrementalBackupTask(chosenFileForMigration, backupChains, srcDatastore, destDataStore); + futures.add(submitKvmIncrementalMigration(srcDatastore.getScope().getScopeId(), task)); + logger.debug("Incremental backup migration {} submitted to incremental pool.", chosenFileForMigration.getUuid()); + } else { + createMigrateDataTask(chosenFileForMigration, snapshotChains, templateChains, srcDatastore, destDataStore, futures); + } + + return storageCapacities; + } + + private void migrateKvmIncrementalBackupChain(DataObject chosenFileForMigration, Map>, Long>> backupChains, DataStore srcDatastore, DataStore destDataStore) { + Transaction.execute((TransactionCallback) status -> { + MigrateBetweenSecondaryStoragesCommandAnswer answer = null; + + try { + List> backupChain = backupChains.get(chosenFileForMigration).first(); + MigrateBackupsBetweenSecondaryStoragesCommand migrateBetweenSecondaryStoragesCmd = new MigrateBackupsBetweenSecondaryStoragesCommand(backupChain.stream().map(list -> list.stream().map(BackupObject::getTO).collect(Collectors.toList())) + .collect(Collectors.toList()), srcDatastore.getTO(), destDataStore.getTO()); + + HostVO host = getAvailableHost(((BackupObject) chosenFileForMigration).getZoneId()); + if (host == null) { + throw new CloudRuntimeException("No hosts found to send migrate command."); + } + + migrateBetweenSecondaryStoragesCmd.setWait(StorageManager.AgentMaxDataMigrationWaitTime.valueIn(host.getClusterId())); + answer = (MigrateBetweenSecondaryStoragesCommandAnswer) agentManager.send(host.getId(), migrateBetweenSecondaryStoragesCmd); + if (answer == null || !answer.getResult()) { + logger.warn("Unable to migrate backups [{}].", backupChain); + throw new CloudRuntimeException("Unable to migrate KVM incremental backups to another secondary storage"); + } + + } catch (final OperationTimedoutException | AgentUnavailableException e) { + throw new CloudRuntimeException("Error while migrating KVM incremental backup chain. Check the logs for more information.", e); + } finally { + if (answer != null) { + updateBackupsReference(destDataStore, answer); + } + } + return answer.getResult(); + }); + } + + private void updateBackupsReference(DataStore destDataStore, MigrateBetweenSecondaryStoragesCommandAnswer answer) { + for (Pair backupIdAndUpdatedCheckpointPath : answer.getMigratedResources()) { + Long backupId = backupIdAndUpdatedCheckpointPath.first(); + BackupDetailVO backupDetail = backupDetailDao.findDetail(backupId, BackupDetailsDao.IMAGE_STORE_ID); + String destDataStoreId = String.valueOf(destDataStore.getId()); + + if (backupDetail == null) { + logger.warn("No details found for backup [{}]. Creating new entry with image store ID [{}].", backupId, destDataStoreId); + backupDetailDao.addDetail(backupId, BackupDetailsDao.IMAGE_STORE_ID, destDataStoreId, false); + continue; + } + + backupDetail.setValue(destDataStoreId); + backupDetailDao.update(backupDetail.getId(), backupDetail); + } + } - MigrateDataTask task = new MigrateDataTask(chosenFileForMigration, srcDatastore, dataStoreManager.getDataStore(destDatastoreId, DataStoreRole.Image)); - if (chosenFileForMigration instanceof SnapshotInfo ) { + private HostVO getAvailableHost(long zoneId) throws AgentUnavailableException, OperationTimedoutException { + List hosts = hostDao.listByDataCenterIdAndHypervisorType(zoneId, Hypervisor.HypervisorType.KVM); + if (CollectionUtils.isNotEmpty(hosts)) { + return hosts.get(new Random().nextInt(hosts.size())); + } + + return null; + } + + private void createMigrateDataTask(DataObject chosenFileForMigration, Map, Long>> snapshotChains, Map, Long>> templateChains, DataStore srcDatastore, DataStore destDataStore, List> futures) { + MigrateDataTask task = new MigrateDataTask(chosenFileForMigration, srcDatastore, destDataStore); + if (chosenFileForMigration instanceof SnapshotInfo) { task.setSnapshotChains(snapshotChains); } if (chosenFileForMigration instanceof TemplateInfo) { @@ -371,7 +471,6 @@ protected Map> migrateAway( } futures.add(submit(srcDatastore.getScope().getScopeId(), task)); logger.debug("Migration of {}: {} is initiated.", chosenFileForMigration.getType().name(), chosenFileForMigration.getUuid()); - return storageCapacities; } protected Future submit(Long zoneId, Callable task) { @@ -390,6 +489,13 @@ protected Future submit(Long zoneId, Callable task) { } + protected synchronized Future submitKvmIncrementalMigration(Long zoneId, Callable task) { + if (!zoneKvmIncrementalExecutorMap.containsKey(zoneId)) { + zoneKvmIncrementalExecutorMap.put(zoneId, Executors.newSingleThreadExecutor()); + } + return zoneKvmIncrementalExecutorMap.get(zoneId).submit(task); + } + protected void scaleExecutorIfNecessary(Long zoneId) { long activeSsvms = migrationHelper.activeSSVMCount(zoneId); long totalJobs = activeSsvms * numConcurrentCopyTasksPerSSVM; @@ -666,4 +772,32 @@ public TemplateApiResult call() { return result; } } + + private class MigrateKvmIncrementalBackupTask implements Callable { + private final DataObject chosenFile; + private final Map>, Long>> backupChains; + private final DataStore srcDataStore; + private final DataStore destDataStore; + + public MigrateKvmIncrementalBackupTask(DataObject chosenFile, Map>, Long>> backupChains, DataStore srcDataStore, DataStore destDataStore) { + this.chosenFile = chosenFile; + this.backupChains = backupChains; + this.srcDataStore = srcDataStore; + this.destDataStore = destDataStore; + } + + @Override + public DataObjectResult call() { + try { + migrateKvmIncrementalBackupChain(chosenFile, backupChains, srcDataStore, destDataStore); + return new DataObjectResult(chosenFile); + } catch (Exception e) { + logger.warn("Failed migrating incremental backup {} due to {}.", chosenFile.getUuid(), e); + DataObjectResult result = new DataObjectResult(chosenFile); + result.setResult(e.toString()); + return result; + } + } + } + } diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java index 5eef84e354cb..f4198819dd16 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestrator.java @@ -38,17 +38,22 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.agent.AgentManager; +import com.cloud.deploy.DeploymentClusterPlanner; import com.cloud.exception.ResourceAllocationException; +import com.cloud.storage.clvm.ClvmPoolManager; import com.cloud.resourcelimit.ReservationHelper; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.snapshot.SnapshotManager; import com.cloud.user.AccountManager; import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy; import org.apache.cloudstack.api.command.admin.vm.MigrateVMCmd; import org.apache.cloudstack.api.command.admin.volume.MigrateVolumeCmdByAdmin; import org.apache.cloudstack.api.command.user.volume.MigrateVolumeCmd; +import org.apache.cloudstack.backup.InternalBackupService; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.ChapInfo; @@ -56,6 +61,8 @@ import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProvider; +import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStoreDriver; import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory; @@ -74,6 +81,7 @@ import org.apache.cloudstack.framework.config.ConfigDepot; 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.framework.jobs.AsyncJobManager; import org.apache.cloudstack.framework.jobs.impl.AsyncJobVO; import org.apache.cloudstack.resourcedetail.DiskOfferingDetailVO; @@ -82,9 +90,18 @@ import org.apache.cloudstack.secret.PassphraseVO; import org.apache.cloudstack.secret.dao.PassphraseDao; import org.apache.cloudstack.snapshot.SnapshotHelper; +import org.apache.cloudstack.kms.KMSManager; +import org.apache.cloudstack.kms.KMSKeyVO; +import org.apache.cloudstack.kms.KMSWrappedKeyVO; +import org.apache.cloudstack.kms.dao.KMSKeyDao; +import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao; +import org.apache.cloudstack.framework.kms.KMSException; +import org.apache.cloudstack.framework.kms.WrappedKey; import org.apache.cloudstack.storage.command.CommandResult; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; @@ -163,7 +180,7 @@ import com.cloud.vm.DiskProfile; import com.cloud.vm.SecondaryStorageVmVO; import com.cloud.vm.UserVmCloneSettingVO; -import com.cloud.vm.UserVmDetailVO; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.VMInstanceVO; import com.cloud.vm.VirtualMachine; @@ -178,7 +195,7 @@ import com.cloud.vm.dao.SecondaryStorageVmDao; import com.cloud.vm.dao.UserVmCloneSettingDao; import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; public class VolumeOrchestrator extends ManagerBase implements VolumeOrchestrationService, Configurable { @@ -201,6 +218,8 @@ public enum UserVmCloneType { @Inject protected PrimaryDataStoreDao _storagePoolDao = null; @Inject + protected ImageStoreDao imageStoreDao; + @Inject protected TemplateDataStoreDao _vmTemplateStoreDao = null; @Inject protected VolumeDao _volumeDao; @@ -249,7 +268,7 @@ public enum UserVmCloneType { @Inject TemplateService templateService; @Inject - UserVmDetailsDao userVmDetailsDao; + VMInstanceDetailsDao vmInstanceDetailsDao; @Inject private SecondaryStorageVmDao secondaryStorageVmDao; @Inject @@ -261,11 +280,31 @@ public enum UserVmCloneType { @Inject DiskOfferingDao diskOfferingDao; @Inject + ConfigDepot configDepot; + @Inject + ConfigurationDao configurationDao; + @Inject VMInstanceDao vmInstanceDao; + @Inject + ClvmPoolManager clvmPoolManager; + @Inject + AgentManager _agentMgr; @Inject protected SnapshotHelper snapshotHelper; + @Inject + private DataStoreProviderManager dataStoreProviderMgr; + @Inject + private KMSManager kmsManager; + @Inject + private KMSKeyDao kmsKeyDao; + @Inject + private KMSWrappedKeyDao kmsWrappedKeyDao; + + @Inject + private InternalBackupService internalBackupService; + private final StateMachine2 _volStateMachine; protected List _storagePoolAllocators; @@ -301,7 +340,7 @@ public VolumeInfo moveVolume(VolumeInfo volumeInfo, long destPoolDcId, Long dest // Find a destination storage pool with the specified criteria DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volumeInfo.getDiskOfferingId()); DiskProfile dskCh = new DiskProfile(volumeInfo.getId(), volumeInfo.getVolumeType(), volumeInfo.getName(), diskOffering.getId(), diskOffering.getDiskSize(), diskOffering.getTagsArray(), - diskOffering.isUseLocalStorage(), diskOffering.isRecreatable(), null, (diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null)); + diskOffering.isUseLocalStorage(), diskOffering.isRecreatable(), null, (diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null)); dskCh.setHyperType(dataDiskHyperType); storageMgr.setDiskProfileThrottling(dskCh, null, diskOffering); @@ -336,9 +375,13 @@ public VolumeVO allocateDuplicateVolumeVO(Volume oldVol, DiskOffering diskOfferi newVol.setInstanceId(oldVol.getInstanceId()); newVol.setRecreatable(oldVol.isRecreatable()); newVol.setFormat(oldVol.getFormat()); - if ((diskOffering == null || diskOffering.getEncrypt()) && oldVol.getPassphraseId() != null) { - PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true)); - newVol.setPassphraseId(passphrase.getId()); + if ((diskOffering == null || diskOffering.getEncrypt())) { + if (oldVol.getKmsKeyId() != null) { + newVol.setKmsKeyId(oldVol.getKmsKeyId()); + } else if (oldVol.getPassphraseId() != null) { + PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true)); + newVol.setPassphraseId(passphrase.getId()); + } } return _volsDao.persist(newVol); @@ -493,7 +536,9 @@ public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, Use DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, volume.getDiskOfferingId()); if (diskOffering.getEncrypt()) { VolumeVO vol = (VolumeVO) volume; - volume = setPassphraseForVolumeEncryption(vol); + // Retrieve KMS key from volume's kmsKeyId if provided + KMSKeyVO kmsKey = getKmsKeyFromVolume(vol); + volume = setPassphraseForVolumeEncryption(vol, kmsKey, volume.getAccountId()); } DataCenter dc = _entityMgr.findById(DataCenter.class, volume.getDataCenterId()); DiskProfile dskCh = new DiskProfile(volume, diskOffering, snapshot.getHypervisorType()); @@ -566,21 +611,34 @@ public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, Use } VolumeInfo vol = volFactory.getVolume(volume.getId()); + long zoneId = volume.getDataCenterId(); DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); - DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot); - SnapshotInfo snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, volume.getDataCenterId()); - - boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole); + DataStoreRole dataStoreRole = snapshotHelper.getDataStoreRole(snapshot, zoneId); + SnapshotInfo snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, zoneId); + boolean kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole, zoneId); + logger.debug("Creating volume from snapshot, with dataStore role {} and on primary storage: {}", dataStoreRole, kvmSnapshotOnlyInPrimaryStorage); + boolean storageSupportSnapshotToTemplateEnabled = snapshotHelper.isStorageSupportSnapshotToTemplate(snapInfo); // storageSupportSnapshotToTemplateEnabled is true only for StorPool now [TODO: Update to check storage supports snapshot to volume (DataStoreCapabilities.CAN_CREATE_VOLUME_FROM_SNAPSHOT) - may impact other storages, or StorPool storage type only] try { - snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); + if (!storageSupportSnapshotToTemplateEnabled) { + dataStoreRole = snapshotHelper.getDataStoreRole(snapshot); + snapInfo = snapshotFactory.getSnapshotWithRoleAndZone(snapshot.getId(), dataStoreRole, zoneId); + kvmSnapshotOnlyInPrimaryStorage = snapshotHelper.isKvmSnapshotOnlyInPrimaryStorage(snapshot, dataStoreRole); + logger.debug("Creating volume from snapshot, with dataStore role {} and on primary storage: {}", dataStoreRole, kvmSnapshotOnlyInPrimaryStorage); + snapInfo = snapshotHelper.backupSnapshotToSecondaryStorageIfNotExists(snapInfo, dataStoreRole, snapshot, kvmSnapshotOnlyInPrimaryStorage); + } } catch (CloudRuntimeException e) { snapshotHelper.expungeTemporarySnapshot(kvmSnapshotOnlyInPrimaryStorage, snapInfo); throw e; } + boolean kvmIncrementalSnapshot = SnapshotManager.kvmIncrementalSnapshot.valueIn(_hostDao.findClusterIdByVolumeInfo(snapInfo.getBaseVolume())); + if (kvmIncrementalSnapshot && DataStoreRole.Image.equals(dataStoreRole)) { + snapInfo = snapshotHelper.convertSnapshotIfNeeded(snapInfo); + } + // don't try to perform a sync if the DataStoreRole of the snapshot is equal to DataStoreRole.Primary - if (!DataStoreRole.Primary.equals(dataStoreRole) || kvmSnapshotOnlyInPrimaryStorage) { + if (!DataStoreRole.Primary.equals(dataStoreRole) || (kvmSnapshotOnlyInPrimaryStorage && !storageSupportSnapshotToTemplateEnabled)) { try { // sync snapshot to region store if necessary DataStore snapStore = snapInfo.getDataStore(); @@ -617,7 +675,7 @@ public VolumeInfo createVolumeFromSnapshot(Volume volume, Snapshot snapshot, Use } protected DiskProfile createDiskCharacteristics(VolumeInfo volumeInfo, VirtualMachineTemplate template, DataCenter dc, DiskOffering diskOffering) { - boolean requiresEncryption = diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null; + boolean requiresEncryption = diskOffering.getEncrypt() || volumeInfo.getPassphraseId() != null || volumeInfo.getKmsKeyId() != null; if (volumeInfo.getVolumeType() == Type.ROOT && Storage.ImageFormat.ISO != template.getFormat()) { String templateToString = getReflectOnlySelectedFields(template); String zoneToString = getReflectOnlySelectedFields(dc); @@ -697,7 +755,9 @@ public VolumeInfo createVolume(VolumeInfo volumeInfo, VirtualMachine vm, Virtual if (diskOffering.getEncrypt()) { VolumeVO vol = _volsDao.findById(volumeInfo.getId()); - setPassphraseForVolumeEncryption(vol); + // Retrieve KMS key from volume's kmsKeyId if provided + KMSKeyVO kmsKey = getKmsKeyFromVolume(vol); + setPassphraseForVolumeEncryption(vol, kmsKey, vol.getAccountId()); volumeInfo = volFactory.getVolume(volumeInfo.getId()); } } @@ -718,6 +778,17 @@ public VolumeInfo createVolume(VolumeInfo volumeInfo, VirtualMachine vm, Virtual logger.debug("Trying to create volume [{}] on storage pool [{}].", volumeToString, poolToString); DataStore store = dataStoreMgr.getDataStore(pool.getId(), DataStoreRole.Primary); + + // For CLVM pools, set the lock host hint so volume is created on the correct host + // This avoids the need for shared mode activation and improves performance + if (ClvmPoolManager.isClvmPoolType(pool.getPoolType()) && hostId != null) { + logger.info("CLVM pool detected. Setting lock host {} for volume {} to route creation to correct host", + hostId, volumeInfo.getUuid()); + volumeInfo.setDestinationHostId(hostId); + + clvmPoolManager.setClvmLockHostId(volumeInfo.getId(), hostId); + } + for (int i = 0; i < 2; i++) { // retry one more time in case of template reload is required for Vmware case AsyncCallFuture future = null; @@ -759,6 +830,122 @@ private String getVolumeIdentificationInfos(Volume volume) { return String.format("uuid: %s, name: %s", volume.getUuid(), volume.getName()); } + /** + * Updates the CLVM_LOCK_HOST_ID for a migrated volume if applicable. + * For CLVM volumes that are attached to a VM, this updates the lock host tracking + * to point to the VM's current host after volume migration. + * + * @param migratedVolume The volume that was migrated + * @param destPool The destination storage pool + * @param operationType Description of the operation (e.g., "migrated", "live-migrated") for logging + */ + private void updateClvmLockHostAfterMigration(Volume migratedVolume, StoragePool destPool, String operationType) { + if (migratedVolume == null || destPool == null) { + return; + } + + StoragePoolVO pool = _storagePoolDao.findById(destPool.getId()); + if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) { + return; + } + + if (migratedVolume.getInstanceId() == null) { + return; + } + + VMInstanceVO vm = vmInstanceDao.findById(migratedVolume.getInstanceId()); + if (vm == null || vm.getHostId() == null) { + return; + } + + clvmPoolManager.setClvmLockHostId(migratedVolume.getId(), vm.getHostId()); + logger.debug("Updated CLVM_LOCK_HOST_ID for {} volume {} to host {} where VM {} is running", + operationType, migratedVolume.getUuid(), vm.getHostId(), vm.getInstanceName()); + } + + /** + * Retrieves the CLVM lock host ID from any existing volume of the specified VM. + * This is useful when attaching a new volume to a stopped VM - we want to maintain + * consistency by using the same host that manages the VM's other CLVM volumes. + * + * @param vmId The ID of the VM + * @return The host ID if found, null otherwise + */ + private Long getClvmLockHostFromVmVolumes(Long vmId) { + if (vmId == null) { + return null; + } + + List vmVolumes = _volsDao.findByInstance(vmId); + if (vmVolumes == null || vmVolumes.isEmpty()) { + return null; + } + + for (VolumeVO volume : vmVolumes) { + if (volume.getPoolId() == null) { + continue; + } + + StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); + if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) { + continue; + } + Long lockHostId = clvmPoolManager.getClvmLockHostId( + volume.getId(), + volume.getUuid(), + volume.getPath(), + pool, + true + ); + if (lockHostId != null) { + logger.debug("Found actual CLVM lock host {} from volume {} of VM {} via LVM query", + lockHostId, volume.getUuid(), vmId); + return lockHostId; + } + } + + return null; + } + + private void transferClvmLocksForVmStart(List volumes, Long destHostId, VMInstanceVO vm) { + if (volumes == null || volumes.isEmpty() || destHostId == null) { + return; + } + + for (VolumeVO volume : volumes) { + if (volume.getPoolId() == null) { + continue; + } + + StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); + if (pool == null || !ClvmPoolManager.isClvmPoolType(pool.getPoolType())) { + continue; + } + + Long currentLockHost = clvmPoolManager.getClvmLockHostId( + volume.getId(), + volume.getUuid(), + volume.getPath(), + pool, + true + ); + + if (currentLockHost == null) { + clvmPoolManager.setClvmLockHostId(volume.getId(), destHostId); + } else if (!currentLockHost.equals(destHostId)) { + logger.info("CLVM volume {} is locked on host {} but VM {} starting on host {}. Transferring lock.", + volume.getUuid(), currentLockHost, vm.getInstanceName(), destHostId); + + if (!clvmPoolManager.transferClvmVolumeLock(volume.getUuid(), volume.getId(), + volume.getPath(), pool, currentLockHost, destHostId)) { + throw new CloudRuntimeException( + String.format("Failed to transfer CLVM lock for volume %s from host %s to host %s", + volume.getUuid(), currentLockHost, destHostId)); + } + } + } + } + public String getRandomVolumeName() { return UUID.randomUUID().toString(); } @@ -834,8 +1021,10 @@ protected DiskProfile toDiskProfile(Volume vol, DiskOffering offering) { @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating volume", create = true) @Override - public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, VirtualMachineTemplate template, Account owner, - Long deviceId, boolean incrementResourceCount) { + public DiskProfile allocateRawVolume( + Type type, String name, DiskOffering offering, Long size, Long minIops, Long maxIops, VirtualMachine vm, + VirtualMachineTemplate template, Account owner, Long deviceId, Long kmsKeyId, boolean incrementResourceCount + ) { if (size == null) { size = offering.getDiskSize(); } else { @@ -868,6 +1057,11 @@ public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offeri vol.setDisplayVolume(userVm.isDisplayVm()); } + // Set KMS key ID if provided + if (kmsKeyId != null) { + vol.setKmsKeyId(kmsKeyId); + } + vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType())); vol = _volsDao.persist(vol); @@ -876,7 +1070,7 @@ public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offeri // Save usage event and update resource count for user vm volumes if (vm.getType() == VirtualMachine.Type.User && incrementResourceCount) { UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offering.getId(), null, size, - Volume.class.getName(), vol.getUuid(), vol.isDisplayVolume()); + Volume.class.getName(), vol.getUuid(), vol.getInstanceId(), vol.isDisplayVolume()); _resourceLimitMgr.incrementVolumeResourceCount(vm.getAccountId(), vol.isDisplayVolume(), vol.getSize(), offering); } DiskProfile diskProfile = toDiskProfile(vol, offering); @@ -887,10 +1081,20 @@ public DiskProfile allocateRawVolume(Type type, String name, DiskOffering offeri } private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, - Account owner, long deviceId, String configurationId) { + Account owner, long deviceId, String configurationId, Long kmsKeyId, Volume volume, Snapshot snapshot) { assert (template.getFormat() != ImageFormat.ISO) : "ISO is not a template."; - Long size = _tmpltMgr.getTemplateSize(template, vm.getDataCenterId()); + if (volume != null) { + volume = attachExistingVolumeToVm(vm, deviceId, volume, type); + provideVmInfoToTheStorageVolume(vm, volume); + return toDiskProfile(volume, offering); + } + Long size; + if (snapshot != null) { + size = _volsDao.findByIdIncludingRemoved(snapshot.getVolumeId()).getSize(); + } else { + size = _tmpltMgr.getTemplateSize(template, vm.getDataCenterId()); + } if (rootDisksize != null) { if (template.isDeployAsIs()) { // Volume size specified from template deploy-as-is @@ -927,6 +1131,11 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering vol.setDisplayVolume(userVm.isDisplayVm()); } + // Set KMS key ID if provided + if (kmsKeyId != null) { + vol.setKmsKeyId(kmsKeyId); + } + vol = _volsDao.persist(vol); saveVolumeDetails(offering.getId(), vol.getId()); @@ -944,13 +1153,49 @@ private DiskProfile allocateTemplatedVolume(Type type, String name, DiskOffering } UsageEventUtils.publishUsageEvent(EventTypes.EVENT_VOLUME_CREATE, vol.getAccountId(), vol.getDataCenterId(), vol.getId(), vol.getName(), offeringId, vol.getTemplateId(), size, - Volume.class.getName(), vol.getUuid(), vol.isDisplayVolume()); + Volume.class.getName(), vol.getUuid(), vol.getInstanceId(), vol.isDisplayVolume()); _resourceLimitMgr.incrementVolumeResourceCount(vm.getAccountId(), vol.isDisplayVolume(), vol.getSize(), offering); } + if (snapshot != null) { + UserVmVO userVmVO = _userVmDao.findById(vm.getId()); + try { + VolumeInfo volumeInfo = createVolumeFromSnapshot(vol, snapshot, userVmVO); + return toDiskProfile(volumeInfo, offering); + } catch (StorageUnavailableException ex) { + throw new CloudRuntimeException("Could not create volume from a snapshot", ex); + } + } return toDiskProfile(vol, offering); } + private void provideVmInfoToTheStorageVolume(VirtualMachine vm, Volume volume) { + + StoragePoolVO pool = _storagePoolDao.findById(volume.getPoolId()); + if (pool != null) { + DataStoreProvider storeProvider = dataStoreProviderMgr + .getDataStoreProvider(pool.getStorageProviderName()); + DataStoreDriver storeDriver = storeProvider.getDataStoreDriver(); + if (storeDriver != null && storeDriver instanceof PrimaryDataStoreDriver && ((PrimaryDataStoreDriver) storeDriver).isVmInfoNeeded()) { + ((PrimaryDataStoreDriver) storeDriver).provideVmInfo(vm.getId(), volume.getId()); + } + } + } + + private Volume attachExistingVolumeToVm(VirtualMachine vm, long deviceId, Volume volume, Type type) { + VolumeVO volumeVO = _volumeDao.findById(volume.getId()); + if (volumeVO == null) { + throw new CloudRuntimeException(String.format("Could not find the volume %s in the DB", volume)); + } + volumeVO.setDeviceId(deviceId); + volumeVO.setVolumeType(type); + if (vm != null) { + volumeVO.setInstanceId(vm.getId()); + } + _volumeDao.update(volumeVO.getId(), volumeVO); + return volumeVO; + } + @Override public void saveVolumeDetails(Long diskOfferingId, Long volumeId) { List volumeDetailsVO = new ArrayList<>(); @@ -980,7 +1225,7 @@ public void saveVolumeDetails(Long diskOfferingId, Long volumeId) { @ActionEvent(eventType = EventTypes.EVENT_VOLUME_CREATE, eventDescription = "creating ROOT volume", create = true) @Override public List allocateTemplatedVolumes(Type type, String name, DiskOffering offering, Long rootDisksize, Long minIops, Long maxIops, VirtualMachineTemplate template, VirtualMachine vm, - Account owner) { + Account owner, Long kmsKeyId, Volume volume, Snapshot snapshot) { String templateToString = getReflectOnlySelectedFields(template); int volumesNumber = 1; @@ -993,7 +1238,7 @@ public List allocateTemplatedVolumes(Type type, String name, DiskOf logger.info("Could not find a running SSVM in datacenter [{}] for deploying VM as is. Not deploying VM [{}] as is.", vm.getDataCenterId(), vm); } else { - UserVmDetailVO configurationDetail = userVmDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_AS_IS_CONFIGURATION); + VMInstanceDetailVO configurationDetail = vmInstanceDetailsDao.findDetail(vm.getId(), VmDetailConstants.DEPLOY_AS_IS_CONFIGURATION); if (configurationDetail != null) { configurationId = configurationDetail.getValue(); } @@ -1027,7 +1272,7 @@ public List allocateTemplatedVolumes(Type type, String name, DiskOf } logger.info("Adding disk object [{}] to VM [{}]", volumeName, vm); DiskProfile diskProfile = allocateTemplatedVolume(type, volumeName, offering, volumeSize, minIops, maxIops, - template, vm, owner, deviceId, configurationId); + template, vm, owner, deviceId, configurationId, kmsKeyId, volume, snapshot); profiles.add(diskProfile); } @@ -1053,7 +1298,7 @@ private void updateRootDiskVolumeEventDetails(Type type, VirtualMachine vm, List callContext.setEventResourceId(volumeIds.get(0)); } String volumeUuids = volumeIds.stream().map(volumeId -> this._uuidMgr.getUuid(Volume.class, volumeId)).collect(Collectors.joining(", ")); - callContext.setEventDetails("Volume Type: " + type + "Volume Id: " + volumeUuids + " Vm Id: " + this._uuidMgr.getUuid(VirtualMachine.class, vm.getId())); + callContext.setEventDetails("Volume Type: " + type + "Volume ID: " + volumeUuids + " Instance ID: " + vm.getUuid()); } } @@ -1062,13 +1307,13 @@ private void handleRootDiskControllerTpeForDeployAsIs(List disksAsIs String diskControllerSubType = disksAsIs.get(0).getDiskControllerSubType(); if (StringUtils.isNotBlank(diskControllerSubType)) { long vmId = vm.getId(); - UserVmDetailVO detail = userVmDetailsDao.findDetail(vmId, VmDetailConstants.ROOT_DISK_CONTROLLER); + VMInstanceDetailVO detail = vmInstanceDetailsDao.findDetail(vmId, VmDetailConstants.ROOT_DISK_CONTROLLER); if (detail != null) { detail.setValue(diskControllerSubType); - userVmDetailsDao.update(detail.getId(), detail); + vmInstanceDetailsDao.update(detail.getId(), detail); } else { - detail = new UserVmDetailVO(vmId, VmDetailConstants.ROOT_DISK_CONTROLLER, diskControllerSubType, false); - userVmDetailsDao.persist(detail); + detail = new VMInstanceDetailVO(vmId, VmDetailConstants.ROOT_DISK_CONTROLLER, diskControllerSubType, false); + vmInstanceDetailsDao.persist(detail); } } } @@ -1117,24 +1362,42 @@ private VolumeInfo copyVolume(StoragePool rootDiskPool, VolumeInfo volumeInfo, V } @Override - public VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volumeInfo, HypervisorType rootDiskHyperType, StoragePool storagePool) throws NoTransitionException { + public VolumeInfo createVolumeOnPrimaryStorage(VirtualMachine vm, VolumeInfo volumeInfo, HypervisorType rootDiskHyperType, StoragePool storagePool, Long clusterId, Long podId) + throws NoTransitionException { String volumeToString = getReflectOnlySelectedFields(volumeInfo.getVolume()); VirtualMachineTemplate rootDiskTmplt = _entityMgr.findById(VirtualMachineTemplate.class, vm.getTemplateId()); DataCenter dcVO = _entityMgr.findById(DataCenter.class, vm.getDataCenterId()); - logger.trace("storage-pool {}/{} is associated with pod {}",storagePool.getName(), storagePool.getUuid(), storagePool.getPodId()); - Long podId = storagePool.getPodId() != null ? storagePool.getPodId() : vm.getPodIdToDeployIn(); + + if (storagePool != null) { + logger.trace("storage-pool {}/{} is associated with pod {}", storagePool.getName(), storagePool.getUuid(), storagePool.getPodId()); + podId = storagePool.getPodId() != null ? storagePool.getPodId() : vm.getPodIdToDeployIn(); + clusterId = storagePool.getClusterId(); + logger.trace("storage-pool {}/{} is associated with cluster {}",storagePool.getName(), storagePool.getUuid(), clusterId); + } + Pod pod = _entityMgr.findById(Pod.class, podId); ServiceOffering svo = _entityMgr.findById(ServiceOffering.class, vm.getServiceOfferingId()); DiskOffering diskVO = _entityMgr.findById(DiskOffering.class, volumeInfo.getDiskOfferingId()); - Long clusterId = storagePool.getClusterId(); - logger.trace("storage-pool {}/{} is associated with cluster {}",storagePool.getName(), storagePool.getUuid(), clusterId); + Long hostId = vm.getHostId(); - if (hostId == null && storagePool.isLocal()) { - List poolHosts = storagePoolHostDao.listByPoolId(storagePool.getId()); - if (poolHosts.size() > 0) { - hostId = poolHosts.get(0).getHostId(); + if (hostId == null && storagePool != null && (storagePool.isLocal() || ClvmPoolManager.isClvmPoolType(storagePool.getPoolType()))) { + if (ClvmPoolManager.isClvmPoolType(storagePool.getPoolType())) { + hostId = getClvmLockHostFromVmVolumes(vm.getId()); + if (hostId != null) { + logger.debug("Using CLVM lock host {} from VM {}'s existing volumes for new volume creation", + hostId, vm.getUuid()); + } + } + + if (hostId == null) { + List poolHosts = storagePoolHostDao.listByPoolId(storagePool.getId()); + if (!poolHosts.isEmpty()) { + hostId = poolHosts.get(0).getHostId(); + logger.debug("Selected host {} from storage pool {} for stopped VM {} volume creation", + hostId, storagePool.getUuid(), vm.getUuid()); + } } } @@ -1293,7 +1556,7 @@ private void destroyVolumeInContext(Volume volume) { // Create new context and inject correct event resource type, id and details, // otherwise VOLUME.DESTROY event will be associated with VirtualMachine and contain VM id and other information. CallContext volumeContext = CallContext.register(CallContext.current(), ApiCommandResourceType.Volume); - volumeContext.setEventDetails("Volume Type: " + volume.getVolumeType() + " Volume Id: " + volume.getUuid() + " Vm Id: " + _uuidMgr.getUuid(VirtualMachine.class, volume.getInstanceId())); + volumeContext.setEventDetails("Volume Type: " + volume.getVolumeType() + " Volume ID: " + volume.getUuid() + " Instance ID: " + _uuidMgr.getUuid(VirtualMachine.class, volume.getInstanceId())); volumeContext.setEventResourceType(ApiCommandResourceType.Volume); volumeContext.setEventResourceId(volume.getId()); try { @@ -1350,7 +1613,7 @@ public Volume migrateVolume(Volume volume, StoragePool destPool) throws StorageU String volumeToString = getVolumeIdentificationInfos(volume); VolumeInfo vol = volFactory.getVolume(volume.getId()); - if (vol == null){ + if (vol == null) { throw new CloudRuntimeException(String.format("Volume migration failed because volume [%s] is null.", volumeToString)); } if (destPool == null) { @@ -1379,6 +1642,10 @@ public Volume migrateVolume(Volume volume, StoragePool destPool) throws StorageU _snapshotDao.updateVolumeIds(vol.getId(), result.getVolume().getId()); _snapshotDataStoreDao.updateVolumeIds(vol.getId(), result.getVolume().getId()); } + internalBackupService.updateVolumeId(vol.getId(), result.getVolume().getId()); + + // For CLVM volumes attached to a VM, update the CLVM_LOCK_HOST_ID after migration + updateClvmLockHostAfterMigration(result.getVolume(), destPool, "migrated"); } return result.getVolume(); } catch (InterruptedException | ExecutionException e) { @@ -1404,6 +1671,10 @@ public Volume liveMigrateVolume(Volume volume, StoragePool destPool) { logger.error("Volume [{}] migration failed due to [{}].", volToString, result.getResult()); return null; } + + // For CLVM volumes attached to a VM, update the CLVM_LOCK_HOST_ID after live migration + updateClvmLockHostAfterMigration(result.getVolume(), destPool, "live-migrated"); + return result.getVolume(); } catch (InterruptedException | ExecutionException e) { logger.error("Volume [{}] migration failed due to [{}].", volToString, e.getMessage()); @@ -1435,6 +1706,8 @@ public void migrateVolumes(VirtualMachine vm, VirtualMachineTO vmTo, Host srcHos throw new CloudRuntimeException(String.format("Failed to find the destination storage pool [%s] to migrate the volume [%s] to.", storagePoolToString, volumeToString)); } + internalBackupService.prepareVolumeForMigration(volume); + volumeMap.put(volFactory.getVolume(volume.getId()), (DataStore)destPool); } @@ -1446,6 +1719,22 @@ public void migrateVolumes(VirtualMachine vm, VirtualMachineTO vmTo, Host srcHos logger.error(msg); throw new CloudRuntimeException(msg); } + for (Map.Entry entry : volumeToPool.entrySet()) { + Volume volume = entry.getKey(); + StoragePool destPool = entry.getValue(); + StoragePoolVO srcPool = _storagePoolDao.findById(volume.getPoolId()); + if (srcPool != null && srcPool.getId() == destPool.getId() && + ClvmPoolManager.isClvmPoolType(srcPool.getPoolType())) { + if (!clvmPoolManager.transferClvmVolumeLock(volume.getUuid(), volume.getId(), + volume.getPath(), srcPool, srcHost.getId(), destHost.getId())) { + throw new CloudRuntimeException(String.format( + "Failed to transfer CLVM lock for volume [%s] to destination host [%s].", + volume.getUuid(), destHost.getId())); + } + } else { + updateClvmLockHostAfterMigration(volume, destPool, "vm-migrated"); + } + } } catch (InterruptedException | ExecutionException e) { logger.error("Failed to migrate VM [{}] along with its volumes due to [{}].", vm, e.getMessage()); logger.debug("Exception: ", e); @@ -1510,12 +1799,8 @@ public void prepareForMigration(VirtualMachineProfile vm, DeployDestination dest vm.addDisk(disk); } - //if (vm.getType() == VirtualMachine.Type.User && vm.getTemplate().getFormat() == ImageFormat.ISO) { if (vm.getType() == VirtualMachine.Type.User) { _tmpltMgr.prepareIsoForVmProfile(vm, dest); - //DataTO dataTO = tmplFactory.getTemplate(vm.getTemplate().getId(), DataStoreRole.Image, vm.getVirtualMachine().getDataCenterId()).getTO(); - //DiskTO iso = new DiskTO(dataTO, 3L, null, Volume.Type.ISO); - //vm.addDisk(iso); } } @@ -1557,7 +1842,7 @@ private Map getDetails(VolumeInfo volumeInfo, DataStore dataStor private void setIoDriverPolicy(Map details, StoragePoolVO storagePool, VolumeVO volume) { if (volume.getInstanceId() != null) { - UserVmDetailVO ioDriverPolicy = userVmDetailsDao.findDetail(volume.getInstanceId(), + VMInstanceDetailVO ioDriverPolicy = vmInstanceDetailsDao.findDetail(volume.getInstanceId(), VmDetailConstants.IO_POLICY); if (ioDriverPolicy != null) { if (IoDriverPolicy.STORAGE_SPECIFIC.toString().equals(ioDriverPolicy.getValue())) { @@ -1706,7 +1991,9 @@ private Pair recreateVolume(VolumeVO vol, VirtualMachinePro if (vol.getState() == Volume.State.Allocated || vol.getState() == Volume.State.Creating) { DiskOffering diskOffering = _entityMgr.findById(DiskOffering.class, vol.getDiskOfferingId()); if (diskOffering.getEncrypt()) { - vol = setPassphraseForVolumeEncryption(vol); + // Retrieve KMS key from volume's kmsKeyId if provided + KMSKeyVO kmsKey = getKmsKeyFromVolume(vol); + vol = setPassphraseForVolumeEncryption(vol, kmsKey, vol.getAccountId()); } newVol = vol; } else { @@ -1782,6 +2069,19 @@ private Pair recreateVolume(VolumeVO vol, VirtualMachinePro future = volService.createManagedStorageVolumeFromTemplateAsync(volume, destPool.getId(), templ, hostId); } else { + // For CLVM pools, set the destination host hint so volume is created on the correct host + // This avoids the need for shared mode activation and improves performance + StoragePoolVO poolVO = _storagePoolDao.findById(destPool.getId()); + if (poolVO != null && ClvmPoolManager.isClvmPoolType(poolVO.getPoolType())) { + Long hostId = vm.getVirtualMachine().getHostId(); + if (hostId != null) { + volume.setDestinationHostId(hostId); + clvmPoolManager.setClvmLockHostId(volume.getId(), hostId); + logger.info("CLVM pool detected during volume creation from template. Setting lock host {} for volume {} (persisted to DB) to route creation to correct host", + hostId, volume.getUuid()); + } + } + future = volService.createVolumeFromTemplateAsync(volume, destPool.getId(), templ); } } @@ -1829,16 +2129,72 @@ private Pair recreateVolume(VolumeVO vol, VirtualMachinePro return new Pair<>(newVol, destPool); } - private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) { - if (volume.getPassphraseId() != null) { + /** + * Helper method to retrieve KMS key from volume's kmsKeyId + */ + private KMSKeyVO getKmsKeyFromVolume(VolumeVO volume) { + if (volume.getKmsKeyId() == null) { + return null; + } + return kmsKeyDao.findById(volume.getKmsKeyId()); + } + + private VolumeVO setKmsKeyForVolumeEncryption(VolumeVO volume, KMSKeyVO kmsKey, Long callerAccountId) { + // Determine caller account ID if not provided + if (callerAccountId == null) { + callerAccountId = volume.getAccountId(); + } + + // Validate permission + if (!kmsManager.hasPermission(callerAccountId, kmsKey)) { + throw new CloudRuntimeException("No permission to use KMS key: " + kmsKey); + } + + try { + logger.debug("Generating and wrapping DEK for volume {} using KMS key {}", volume.getName(), kmsKey.getUuid()); + long startTime = System.currentTimeMillis(); + + // Generate and wrap DEK using active KEK version + WrappedKey wrappedKey = kmsManager.generateVolumeKeyWithKek(kmsKey, callerAccountId); + + // The wrapped key is already persisted by generateVolumeKeyWithKek, get its ID + KMSWrappedKeyVO wrappedKeyVO = kmsWrappedKeyDao.findByUuid(wrappedKey.getUuid()); + if (wrappedKeyVO == null) { + throw new CloudRuntimeException("Failed to find persisted wrapped key: " + wrappedKey.getUuid()); + } + + // Set the wrapped key ID on the volume + volume.setKmsWrappedKeyId(wrappedKeyVO.getId()); + + long finishTime = System.currentTimeMillis(); + logger.debug("Generating and persisting wrapped key took {} ms for volume: {}", + (finishTime - startTime), volume.getName()); + + return _volsDao.persist(volume); + + } catch (KMSException e) { + throw new CloudRuntimeException("KMS failure while setting up volume encryption: " + e.getMessage(), e); + } + } + private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume, KMSKeyVO kmsKey, Long callerAccountId) { + // If volume already has encryption set up, return it + if (volume.getKmsWrappedKeyId() != null || volume.getPassphraseId() != null) { return volume; } - logger.debug("Creating passphrase for the volume: " + volume.getName()); + if (kmsKey != null) { + return setKmsKeyForVolumeEncryption(volume, kmsKey, callerAccountId); + } + // Legacy: passphrase-based encryption (fallback when KMS not enabled or KMS key not specified) + return setPassphraseForVolumeEncryption(volume); + } + + private VolumeVO setPassphraseForVolumeEncryption(VolumeVO volume) { + logger.debug("Creating passphrase for the volume: {}", volume.getName()); long startTime = System.currentTimeMillis(); PassphraseVO passphrase = passphraseDao.persist(new PassphraseVO(true)); volume.setPassphraseId(passphrase.getId()); long finishTime = System.currentTimeMillis(); - logger.debug("Creating and persisting passphrase took: " + (finishTime - startTime) + " ms for the volume: " + volume.toString()); + logger.debug("Creating and persisting passphrase took: {} ms for the volume: {}", finishTime - startTime, volume.toString()); return _volsDao.persist(volume); } @@ -1867,7 +2223,7 @@ protected void updateVolumeSize(DataStore store, VolumeVO vol) throws ResourceAl PrimaryDataStoreDriver driver = (PrimaryDataStoreDriver) store.getDriver(); long newSize = driver.getVolumeSizeRequiredOnPool(vol.getSize(), template == null ? null : template.getSize(), - vol.getPassphraseId() != null); + vol.getPassphraseId() != null || vol.getKmsKeyId() != null); if (newSize == vol.getSize()) { return; @@ -1905,13 +2261,18 @@ public void prepare(VirtualMachineProfile vm, DeployDestination dest) throws Sto throw new CloudRuntimeException(msg); } - // don't allow to start vm that doesn't have a root volume if (_volsDao.findByInstanceAndType(vm.getId(), Volume.Type.ROOT).isEmpty()) { throw new CloudRuntimeException(String.format("ROOT volume is missing, unable to prepare volumes for the VM [%s].", vm.getVirtualMachine())); } List vols = _volsDao.findUsableVolumesForInstance(vm.getId()); + VirtualMachine vmInstance = vm.getVirtualMachine(); + VMInstanceVO vmInstanceVO = vmInstanceDao.findById(vmInstance.getId()); + if (vmInstance.getState() == State.Starting && dest.getHost() != null) { + transferClvmLocksForVmStart(vols, dest.getHost().getId(), vmInstanceVO); + } + List tasks = getTasks(vols, dest.getStorageForDisks(), vm); Volume vol = null; PrimaryDataStore store; @@ -1989,8 +2350,27 @@ public void prepare(VirtualMachineProfile vm, DeployDestination dest) throws Sto _vmCloneSettingDao.persist(vmCloneSettingVO); } } + } + } + @Override + public Pair, Set> getVolumeCheckpointPathsAndImageStoreUrls(long volumeId, HypervisorType hypervisorType) { + List checkpointPaths = new ArrayList<>(); + Set imageStoreIds = new HashSet<>(); + Set imageStoreUrls = new HashSet<>(); + if (HypervisorType.KVM.equals(hypervisorType)) { + List snapshotDataStoreVos = _snapshotDataStoreDao.listReadyByVolumeIdAndCheckpointPathNotNull(volumeId); + snapshotDataStoreVos.forEach(snapshotDataStoreVO -> { + checkpointPaths.add(snapshotDataStoreVO.getKvmCheckpointPath()); + if (DataStoreRole.Image.equals(snapshotDataStoreVO.getRole())) { + imageStoreIds.add(snapshotDataStoreVO.getDataStoreId()); + } + }); + imageStoreUrls = imageStoreIds.stream().map(id -> imageStoreDao.findById(id).getUrl()).collect(Collectors.toSet()); + logger.debug(String.format("Found [%s] snapshots [%s] that have checkpoints for volume with id [%s].", snapshotDataStoreVos.size(), snapshotDataStoreVos, volumeId)); } + + return new Pair<>(checkpointPaths, imageStoreUrls); } private void handleCheckAndRepairVolume(Volume vol, Long hostId) { @@ -2033,7 +2413,9 @@ public boolean canVmRestartOnAnotherServer(long vmId) { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {RecreatableSystemVmEnabled, MaxVolumeSize, StorageHAMigrationEnabled, StorageMigrationEnabled, CustomDiskOfferingMaxSize, CustomDiskOfferingMinSize, VolumeUrlCheck}; + return new ConfigKey[] { + RecreatableSystemVmEnabled, MaxVolumeSize, StorageHAMigrationEnabled, StorageMigrationEnabled, + CustomDiskOfferingMaxSize, CustomDiskOfferingMinSize, VolumeUrlCheck, VolumeAllocationAlgorithm}; } @Override @@ -2046,6 +2428,18 @@ public boolean configure(String name, Map params) throws Configu return true; } + @Override + public boolean start() { + if (configDepot.isNewConfig(VolumeAllocationAlgorithm)) { + String vmAllocationAlgo = DeploymentClusterPlanner.VmAllocationAlgorithm.value(); + if (com.cloud.utils.StringUtils.isNotEmpty(vmAllocationAlgo) && !VolumeAllocationAlgorithm.defaultValue().equalsIgnoreCase(vmAllocationAlgo)) { + logger.debug("Updating value for configuration: {} to {}", VolumeAllocationAlgorithm.key(), vmAllocationAlgo); + configurationDao.update(VolumeAllocationAlgorithm.key(), vmAllocationAlgo); + } + } + return true; + } + private void cleanupVolumeDuringAttachFailure(Long volumeId, Long vmId) { VolumeVO volume = _volsDao.findById(volumeId); if (volume == null) { @@ -2150,7 +2544,8 @@ public void destroyVolume(Volume volume) { if (volume.getState() == Volume.State.Allocated) { _volsDao.remove(volume.getId()); stateTransitTo(volume, Volume.Event.DestroyRequested); - _resourceLimitMgr.decrementVolumeResourceCount(volume.getAccountId(), volume.isDisplay(), volume.getSize(), diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId())); + _resourceLimitMgr.decrementVolumeResourceCount(volume.getAccountId(), volume.isDisplay(), volume.getSize(), diskOfferingDao.findByIdIncludingRemoved(volume.getDiskOfferingId()), + null); } else { destroyVolumeInContext(volume); } @@ -2210,6 +2605,7 @@ public void updateVolumeDiskChain(long volumeId, String path, String chainInfo, StoragePoolVO pool = _storagePoolDao.findByUuid(updatedDataStoreUUID); if (pool != null) { vol.setPoolId(pool.getId()); + vol.setPoolType(pool.getPoolType()); } } _volsDao.update(volumeId, vol); @@ -2219,7 +2615,7 @@ public void updateVolumeDiskChain(long volumeId, String path, String chainInfo, @Override public DiskProfile importVolume(Type type, String name, DiskOffering offering, Long sizeInBytes, Long minIops, Long maxIops, Long zoneId, HypervisorType hypervisorType, VirtualMachine vm, VirtualMachineTemplate template, Account owner, - Long deviceId, Long poolId, String path, String chainInfo) { + Long deviceId, Long poolId, Storage.StoragePoolType poolType, String path, String chainInfo) { if (sizeInBytes == null) { sizeInBytes = offering.getDiskSize(); } @@ -2260,6 +2656,7 @@ public DiskProfile importVolume(Type type, String name, DiskOffering offering, L vol.setFormat(getSupportedImageFormatForCluster(hypervisorType)); vol.setPoolId(poolId); + vol.setPoolType(poolType); vol.setPath(path); vol.setChainInfo(chainInfo); vol.setState(Volume.State.Ready); @@ -2269,7 +2666,7 @@ public DiskProfile importVolume(Type type, String name, DiskOffering offering, L @Override public DiskProfile updateImportedVolume(Type type, DiskOffering offering, VirtualMachine vm, VirtualMachineTemplate template, - Long deviceId, Long poolId, String path, String chainInfo, DiskProfile diskProfile) { + Long deviceId, Long poolId, Storage.StoragePoolType poolType, String path, String chainInfo, DiskProfile diskProfile) { VolumeVO vol = _volsDao.findById(diskProfile.getVolumeId()); if (vm != null) { @@ -2303,6 +2700,7 @@ public DiskProfile updateImportedVolume(Type type, DiskOffering offering, Virtua vol.setFormat(getSupportedImageFormatForCluster(vm.getHypervisorType())); vol.setPoolId(poolId); + vol.setPoolType(poolType); vol.setPath(path); vol.setChainInfo(chainInfo); vol.setSize(diskProfile.getSize()); diff --git a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java index 51e87663919b..ff75aa0cbb65 100644 --- a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java +++ b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/service/api/ProvisioningServiceImpl.java @@ -140,20 +140,12 @@ public List listHosts() { @Override public List listPods() { - /* - * Not in use now, just commented out. - */ - //List pods = new ArrayList(); - //pods.add(new PodEntityImpl("pod-uuid-1", "pod1")); - //pods.add(new PodEntityImpl("pod-uuid-2", "pod2")); return null; } @Override public List listZones() { List zones = new ArrayList(); - //zones.add(new ZoneEntityImpl("zone-uuid-1")); - //zones.add(new ZoneEntityImpl("zone-uuid-2")); return zones; } diff --git a/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml b/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml index 17c5002c718b..e17302e68a1f 100644 --- a/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml +++ b/engine/orchestration/src/main/resources/META-INF/cloudstack/core/spring-engine-orchestration-core-context.xml @@ -44,6 +44,8 @@ value="#{storagePoolAllocatorsRegistry.registered}" /> + + + + diff --git a/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java index 293a988dd2ce..43d83a672c0f 100644 --- a/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/agent/manager/AgentManagerImplTest.java @@ -49,7 +49,7 @@ public void setUp() throws Exception { host = new HostVO("some-Uuid"); host.setDataCenterId(1L); cmds = new StartupCommand[]{new StartupRoutingCommand()}; - attache = new ConnectedAgentAttache(null, 1L, "uuid", "kvm-attache", null, false); + attache = new ConnectedAgentAttache(null, 1L, "uuid", "kvm-attache", Hypervisor.HypervisorType.KVM, null, false); hostDao = Mockito.mock(HostDao.class); storagePoolMonitor = Mockito.mock(Listener.class); diff --git a/engine/orchestration/src/test/java/com/cloud/agent/manager/ConnectedAgentAttacheTest.java b/engine/orchestration/src/test/java/com/cloud/agent/manager/ConnectedAgentAttacheTest.java index 0b42b505668a..66e6bbae5e2c 100644 --- a/engine/orchestration/src/test/java/com/cloud/agent/manager/ConnectedAgentAttacheTest.java +++ b/engine/orchestration/src/test/java/com/cloud/agent/manager/ConnectedAgentAttacheTest.java @@ -22,6 +22,7 @@ import org.junit.Test; +import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.nio.Link; public class ConnectedAgentAttacheTest { @@ -31,8 +32,8 @@ public void testEquals() throws Exception { Link link = mock(Link.class); - ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, link, false); - ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, link, false); + ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link, false); + ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM,link, false); assertTrue(agentAttache1.equals(agentAttache2)); } @@ -42,7 +43,7 @@ public void testEqualsFalseNull() throws Exception { Link link = mock(Link.class); - ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, link, false); + ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link, false); assertFalse(agentAttache1.equals(null)); } @@ -53,8 +54,8 @@ public void testEqualsFalseDiffLink() throws Exception { Link link1 = mock(Link.class); Link link2 = mock(Link.class); - ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, link1, false); - ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, link2, false); + ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false); + ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 0, "uuid", null, Hypervisor.HypervisorType.KVM, link2, false); assertFalse(agentAttache1.equals(agentAttache2)); } @@ -64,8 +65,8 @@ public void testEqualsFalseDiffId() throws Exception { Link link1 = mock(Link.class); - ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, link1, false); - ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 2, "uuid", null, link1, false); + ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false); + ConnectedAgentAttache agentAttache2 = new ConnectedAgentAttache(null, 2, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false); assertFalse(agentAttache1.equals(agentAttache2)); } @@ -75,7 +76,7 @@ public void testEqualsFalseDiffClass() throws Exception { Link link1 = mock(Link.class); - ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, link1, false); + ConnectedAgentAttache agentAttache1 = new ConnectedAgentAttache(null, 1, "uuid", null, Hypervisor.HypervisorType.KVM, link1, false); assertFalse(agentAttache1.equals("abc")); } diff --git a/engine/orchestration/src/test/java/com/cloud/agent/manager/DirectAgentAttacheTest.java b/engine/orchestration/src/test/java/com/cloud/agent/manager/DirectAgentAttacheTest.java index 65e31c271a42..4ba276460e3a 100644 --- a/engine/orchestration/src/test/java/com/cloud/agent/manager/DirectAgentAttacheTest.java +++ b/engine/orchestration/src/test/java/com/cloud/agent/manager/DirectAgentAttacheTest.java @@ -24,6 +24,7 @@ import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.hypervisor.Hypervisor; import com.cloud.resource.ServerResource; import java.util.UUID; @@ -42,7 +43,7 @@ public class DirectAgentAttacheTest { @Before public void setup() { - directAgentAttache = new DirectAgentAttache(_agentMgr, _id, _uuid, "myDirectAgentAttache", _resource, false); + directAgentAttache = new DirectAgentAttache(_agentMgr, _id, _uuid, "myDirectAgentAttache", Hypervisor.HypervisorType.KVM, _resource, false); MockitoAnnotations.initMocks(directAgentAttache); } diff --git a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java index 47f9b9f33e27..c656e1a282af 100644 --- a/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java +++ b/engine/orchestration/src/test/java/com/cloud/vm/VirtualMachineManagerImplTest.java @@ -19,20 +19,30 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -40,18 +50,40 @@ import java.util.UUID; import java.util.stream.Collectors; +import com.cloud.agent.api.UnmanageInstanceAnswer; +import com.cloud.agent.api.UnmanageInstanceCommand; +import com.cloud.agent.api.to.DataTO; +import com.cloud.agent.api.to.DiskTO; +import com.cloud.api.ApiDBUtils; +import com.cloud.event.ActionEventUtils; +import com.cloud.exception.ConcurrentOperationException; +import com.cloud.ha.HighAvailabilityManager; +import com.cloud.network.Network; +import com.cloud.network.NetworkModel; +import com.cloud.resource.ResourceManager; +import com.cloud.storage.clvm.ClvmPoolManager; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.subsystem.api.storage.StoragePoolAllocator; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.impl.ConfigDepotImpl; +import org.apache.cloudstack.framework.extensions.dao.ExtensionDetailsDao; +import org.apache.cloudstack.framework.extensions.manager.ExtensionsManager; +import org.apache.cloudstack.framework.extensions.vo.ExtensionDetailsVO; +import org.apache.cloudstack.framework.jobs.dao.VmWorkJobDao; +import org.apache.cloudstack.framework.jobs.impl.VmWorkJobVO; import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.cloudstack.storage.to.VolumeObjectTO; import org.apache.commons.collections.MapUtils; import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.InjectMocks; import org.mockito.Mock; @@ -64,15 +96,20 @@ import com.cloud.agent.AgentManager; import com.cloud.agent.api.Command; +import com.cloud.agent.api.PrepareExternalProvisioningAnswer; +import com.cloud.agent.api.RebootCommand; +import com.cloud.agent.api.StartCommand; import com.cloud.agent.api.StopAnswer; import com.cloud.agent.api.StopCommand; import com.cloud.agent.api.routing.NetworkElementCommand; +import com.cloud.agent.api.to.NicTO; import com.cloud.agent.api.to.VirtualMachineTO; import com.cloud.api.query.dao.UserVmJoinDao; import com.cloud.api.query.vo.UserVmJoinVO; import com.cloud.dc.ClusterDetailsDao; import com.cloud.dc.ClusterDetailsVO; import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenter; import com.cloud.dc.DataCenterVO; import com.cloud.dc.Pod; import com.cloud.dc.dao.ClusterDao; @@ -85,12 +122,15 @@ import com.cloud.deploy.DeploymentPlanningManager; import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; +import com.cloud.exception.AgentUnavailableException; import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostVO; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.HypervisorGuruManager; +import com.cloud.network.NetworkService; import com.cloud.network.dao.NetworkDao; import com.cloud.network.dao.NetworkVO; import com.cloud.network.vpc.VpcVO; @@ -114,6 +154,7 @@ import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateZoneDao; import com.cloud.storage.dao.VolumeDao; +import com.cloud.storage.snapshot.SnapshotManager; import com.cloud.template.VirtualMachineTemplate; import com.cloud.user.Account; import com.cloud.user.AccountVO; @@ -126,8 +167,9 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.fsm.StateMachine2; import com.cloud.vm.VirtualMachine.State; +import com.cloud.vm.dao.NicDao; import com.cloud.vm.dao.UserVmDao; -import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; import com.cloud.vm.dao.VMInstanceDao; @RunWith(MockitoJUnitRunner.class) @@ -148,17 +190,26 @@ public class VirtualMachineManagerImplTest { private PrimaryDataStoreDao storagePoolDaoMock; @Mock private VMInstanceVO vmInstanceMock; + @Mock + private VmWorkJobDao _workJobDao; + private long vmInstanceVoMockId = 1L; @Mock private ServiceOfferingVO serviceOfferingMock; + @Mock + private SnapshotManager snapshotManagerMock; + @Mock private DiskOfferingVO diskOfferingMock; private long hostMockId = 1L; private long clusterMockId = 2L; private long zoneMockId = 3L; + private final String vmMockUuid = UUID.randomUUID().toString(); + private final String hostUuid = UUID.randomUUID().toString(); + @Mock private HostVO hostMock; @Mock @@ -170,6 +221,7 @@ public class VirtualMachineManagerImplTest { private StoragePoolVO storagePoolVoMock; private long storagePoolVoMockId = 11L; private long storagePoolVoMockClusterId = 234L; + private String vmName = "vm1"; @Mock private VolumeVO volumeVoMock; @@ -209,6 +261,8 @@ public class VirtualMachineManagerImplTest { @Mock private EntityManager _entityMgr; @Mock + private ResourceManager _resourceMgr; + @Mock private DeploymentPlanningManager _dpMgr; @Mock private HypervisorGuruManager _hvGuruMgr; @@ -217,11 +271,29 @@ public class VirtualMachineManagerImplTest { @Mock private ClusterDetailsDao _clusterDetailsDao; @Mock - private UserVmDetailsDao userVmDetailsDao; + private VMInstanceDetailsDao vmInstanceDetailsDao; @Mock private ItWorkDao _workDao; @Mock protected StateMachine2 _stateMachine; + @Mock + ExtensionsManager extensionsManager; + @Mock + ExtensionDetailsDao extensionDetailsDao; + @Mock + NicDao _nicsDao; + @Mock + NetworkService networkService; + @Mock + NetworkModel networkModel; + @Mock + VolumeDataFactory volumeDataFactoryMock; + @Mock + StorageManager storageManager; + @Mock + private HighAvailabilityManager _haMgr; + @Mock + VirtualMachineGuru guru; private ConfigDepotImpl configDepotImpl; private boolean updatedConfigKeyDepot = false; @@ -231,6 +303,7 @@ public void setup() { ReflectionTestUtils.getField(VirtualMachineManager.VmMetadataManufacturer, "s_depot"); virtualMachineManagerImpl.setHostAllocators(new ArrayList<>()); + when(vmInstanceMock.getName()).thenReturn(vmName); when(vmInstanceMock.getId()).thenReturn(vmInstanceVoMockId); when(vmInstanceMock.getServiceOfferingId()).thenReturn(2L); when(hostMock.getId()).thenReturn(hostMockId); @@ -265,6 +338,19 @@ public void cleanup() { } } + private ClvmPoolManager injectMockedClvmPoolManager() { + ClvmPoolManager clvmPoolManagerMock = mock(ClvmPoolManager.class); + ReflectionTestUtils.setField(virtualMachineManagerImpl, "clvmPoolManager", clvmPoolManagerMock); + return clvmPoolManagerMock; + } + + private Method getUpdateClvmLockHostForVmVolumesMethod() throws NoSuchMethodException { + Method method = VirtualMachineManagerImpl.class.getDeclaredMethod( + "updateClvmLockHostForVmVolumes", long.class, long.class); + method.setAccessible(true); + return method; + } + @Test public void testaddHostIpToCertDetailsIfConfigAllows() { Host vmHost = mock(Host.class); @@ -451,8 +537,8 @@ public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentS virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); - Mockito.verify(storagePoolVoMock).isManaged(); - Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId(); + verify(storagePoolVoMock).isManaged(); + verify(storagePoolVoMock, Mockito.times(0)).getId(); } @Test @@ -462,8 +548,8 @@ public void allowVolumeMigrationsForPowerFlexStorage() { virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, Mockito.mock(StoragePoolVO.class)); - Mockito.verify(storagePoolVoMock).isManaged(); - Mockito.verify(storagePoolVoMock, Mockito.times(0)).getId(); + verify(storagePoolVoMock).isManaged(); + verify(storagePoolVoMock, Mockito.times(0)).getId(); } @Test @@ -478,8 +564,8 @@ public void executeManagedStorageChecksWhenTargetStoragePoolProvidedTestCurrentS virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolProvided(storagePoolVoMock, volumeVoMock, storagePoolVoMock); - Mockito.verify(storagePoolVoMock).isManaged(); - Mockito.verify(storagePoolVoMock, Mockito.times(2)).getId(); + verify(storagePoolVoMock).isManaged(); + verify(storagePoolVoMock, Mockito.times(2)).getId(); } @Test(expected = CloudRuntimeException.class) @@ -503,7 +589,7 @@ public void buildMapUsingUserInformationTestUserDefinedMigrationMapEmpty() { Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); - Mockito.verify(userDefinedVolumeToStoragePoolMap, times(0)).keySet(); + verify(userDefinedVolumeToStoragePoolMap, times(0)).keySet(); } @Test(expected = CloudRuntimeException.class) @@ -532,7 +618,7 @@ public void buildMapUsingUserInformationTestTargetHostHasAccessToPool() { assertFalse(volumeToPoolObjectMap.isEmpty()); assertEquals(storagePoolVoMock, volumeToPoolObjectMap.get(volumeVoMock)); - Mockito.verify(userDefinedVolumeToStoragePoolMap, times(1)).keySet(); + verify(userDefinedVolumeToStoragePoolMap, times(1)).keySet(); } @Test @@ -559,8 +645,8 @@ public void executeManagedStorageChecksWhenTargetStoragePoolNotProvidedTestCurre virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.verify(storagePoolVoMock).isManaged(); - Mockito.verify(storagePoolHostDaoMock, Mockito.times(0)).findByPoolHost(anyLong(), anyLong()); + verify(storagePoolVoMock).isManaged(); + verify(storagePoolHostDaoMock, Mockito.times(0)).findByPoolHost(anyLong(), anyLong()); } @Test @@ -570,8 +656,8 @@ public void executeManagedStorageChecksWhenTargetStoragePoolNotProvidedTestCurre virtualMachineManagerImpl.executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.verify(storagePoolVoMock).isManaged(); - Mockito.verify(storagePoolHostDaoMock, Mockito.times(1)).findByPoolHost(storagePoolVoMockId, hostMockId); + verify(storagePoolVoMock).isManaged(); + verify(storagePoolHostDaoMock, Mockito.times(1)).findByPoolHost(storagePoolVoMockId, hostMockId); } @Test(expected = CloudRuntimeException.class) @@ -670,11 +756,11 @@ public void getCandidateStoragePoolsToMigrateLocalVolumeTestMoreThanOneAllocator Assert.assertTrue(poolList.isEmpty()); - Mockito.verify(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), + verify(storagePoolAllocatorMock).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - Mockito.verify(storagePoolAllocatorMock2).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), + verify(storagePoolAllocatorMock2).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); - Mockito.verify(storagePoolAllocatorMock3).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), + verify(storagePoolAllocatorMock3).allocateToPool(any(DiskProfile.class), any(VirtualMachineProfile.class), any(DeploymentPlan.class), any(ExcludeList.class), Mockito.eq(StoragePoolAllocator.RETURN_UPTO_ALL)); } @@ -732,8 +818,8 @@ public void createStoragePoolMappingsForVolumesTestLocalStoragevolume() { virtualMachineManagerImpl.createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, allVolumes); Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); - Mockito.verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.verify(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); + verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); + verify(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); } @Test @@ -751,9 +837,9 @@ public void createStoragePoolMappingsForVolumesTestCrossCluterMigration() { virtualMachineManagerImpl.createStoragePoolMappingsForVolumes(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, allVolumes); Assert.assertTrue(volumeToPoolObjectMap.isEmpty()); - Mockito.verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.verify(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); - Mockito.verify(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); + verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); + verify(virtualMachineManagerImpl).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); + verify(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); } @Test @@ -772,9 +858,9 @@ public void createStoragePoolMappingsForVolumesTestNotCrossCluterMigrationWithCl assertFalse(volumeToPoolObjectMap.isEmpty()); assertEquals(storagePoolVoMock, volumeToPoolObjectMap.get(volumeVoMock)); - Mockito.verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); - Mockito.verify(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); - Mockito.verify(virtualMachineManagerImpl, Mockito.times(0)).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, + verify(virtualMachineManagerImpl).executeManagedStorageChecksWhenTargetStoragePoolNotProvided(hostMock, storagePoolVoMock, volumeVoMock); + verify(virtualMachineManagerImpl).isStorageCrossClusterMigration(clusterMockId, storagePoolVoMock); + verify(virtualMachineManagerImpl, Mockito.times(0)).createVolumeToStoragePoolMappingIfPossible(virtualMachineProfileMock, dataCenterDeploymentMock, volumeToPoolObjectMap, volumeVoMock, storagePoolVoMock); } @@ -1091,7 +1177,7 @@ public void testOrchestrateStartNonNullPodId() throws Exception { when(cluster.getId()).thenReturn(1L); when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_cpu); when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_ram); - when(userVmDetailsDao.findDetail(anyLong(), Mockito.anyString())).thenReturn(null); + when(vmInstanceDetailsDao.findDetail(anyLong(), Mockito.anyString())).thenReturn(null); when(cluster_detail_cpu.getValue()).thenReturn("1.0"); when(cluster_detail_ram.getValue()).thenReturn("1.0"); doReturn(false).when(virtualMachineManagerImpl).areAllVolumesAllocated(Mockito.anyLong()); @@ -1187,7 +1273,7 @@ public void testOrchestrateStartNullPodId() throws Exception { when(cluster.getId()).thenReturn(1L); when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_cpu); when(_clusterDetailsDao.findDetail(1L, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(cluster_detail_ram); - when(userVmDetailsDao.findDetail(anyLong(), Mockito.anyString())).thenReturn(null); + when(vmInstanceDetailsDao.findDetail(anyLong(), Mockito.anyString())).thenReturn(null); when(cluster_detail_cpu.getValue()).thenReturn("1.0"); when(cluster_detail_ram.getValue()).thenReturn("1.0"); doReturn(true).when(virtualMachineManagerImpl).areAllVolumesAllocated(Mockito.anyLong()); @@ -1304,4 +1390,760 @@ public void testUpdateVmMetadataManufacturerAndProductCustomManufacturer() { Assert.assertEquals(manufacturer, to.getMetadataManufacturer()); Assert.assertEquals(product, to.getMetadataProductName()); } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationTestReturnIfNotKvm() { + Mockito.doReturn(HypervisorType.VMware).when(vmInstanceMock).getHypervisorType(); + + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + + verify(volumeDaoMock, never()).findByInstance(Mockito.anyLong()); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationTestReturnIfVolumesDoNotHaveCheckpoints() throws OperationTimedoutException, AgentUnavailableException { + Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); + Mockito.doReturn(new ArrayList()).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); + + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + + verify(agentManagerMock, never()).send(Mockito.anyLong(), (Command) any()); + } + + @Test (expected = CloudRuntimeException.class) + public void recreateCheckpointsKvmOnVmAfterMigrationTestAgentUnavailableThrowsCloudRuntimeExceptionAndEndsSnapshotChains() throws OperationTimedoutException, AgentUnavailableException { + Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); + Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); + + doThrow(new AgentUnavailableException(0)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); + Mockito.doNothing().when(snapshotManagerMock).endSnapshotChainForVolume(Mockito.anyLong(), Mockito.any()); + + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + + verify(snapshotManagerMock, Mockito.times(1)).endSnapshotChainForVolume(Mockito.anyLong(),any()); + } + + @Test (expected = CloudRuntimeException.class) + public void recreateCheckpointsKvmOnVmAfterMigrationTestOperationTimedoutExceptionThrowsCloudRuntimeExceptionAndEndsSnapshotChains() throws OperationTimedoutException, AgentUnavailableException { + Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); + Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); + + doThrow(new OperationTimedoutException(null, 0, 0, 0, false)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); + Mockito.doNothing().when(snapshotManagerMock).endSnapshotChainForVolume(Mockito.anyLong(), Mockito.any()); + + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + + verify(snapshotManagerMock, Mockito.times(1)).endSnapshotChainForVolume(Mockito.anyLong(),any()); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationTestRecreationFails() throws OperationTimedoutException, AgentUnavailableException { + Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); + Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); + + Mockito.doReturn(new com.cloud.agent.api.Answer(null, false, null)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); + Mockito.doNothing().when(snapshotManagerMock).endSnapshotChainForVolume(Mockito.anyLong(), Mockito.any()); + + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + + verify(snapshotManagerMock, Mockito.times(1)).endSnapshotChainForVolume(Mockito.anyLong(),any()); + } + + @Test + public void recreateCheckpointsKvmOnVmAfterMigrationTestRecreationSucceeds() throws OperationTimedoutException, AgentUnavailableException { + Mockito.doReturn(HypervisorType.KVM).when(vmInstanceMock).getHypervisorType(); + Mockito.doReturn(List.of(new VolumeObjectTO())).when(virtualMachineManagerImpl).getVmVolumesWithCheckpointsToRecreate(Mockito.any()); + + Mockito.doReturn(new com.cloud.agent.api.Answer(null, true, null)).when(agentManagerMock).send(Mockito.anyLong(), (Command) any()); + + virtualMachineManagerImpl.recreateCheckpointsKvmOnVmAfterMigration(vmInstanceMock, 0); + + verify(snapshotManagerMock, never()).endSnapshotChainForVolume(Mockito.anyLong(),any()); + } + + @Test + public void updateStartCommandWithExternalDetails_nonExternalHypervisor_noAction() { + Host host = mock(Host.class); + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + StartCommand command = mock(StartCommand.class); + + when(host.getHypervisorType()).thenReturn(HypervisorType.KVM); + + virtualMachineManagerImpl.updateStartCommandWithExternalDetails(host, vmTO, command); + + verify(command, never()).setExternalDetails(any()); + } + + @Test + public void updateStartCommandWithExternalDetails_externalHypervisor_setsExternalDetails() { + Host host = mock(Host.class); + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + StartCommand command = mock(StartCommand.class); + NicTO nic = mock(NicTO.class); + + when(host.getHypervisorType()).thenReturn(HypervisorType.External); + when(vmTO.getExternalDetails()).thenReturn(new HashMap<>()); + when(vmTO.getNics()).thenReturn(new NicTO[]{nic}); + when(nic.isDefaultNic()).thenReturn(true); + when(networkService.getNicVlanValueForExternalVm(nic)).thenReturn("segmentName"); + when(extensionsManager.getExternalAccessDetails(eq(host), any())).thenReturn(new HashMap<>()); + + virtualMachineManagerImpl.updateStartCommandWithExternalDetails(host, vmTO, command); + + verify(command).setExternalDetails(any()); + } + + @Test + public void updateStopCommandForExternalHypervisorType_nonExternalHypervisor_noAction() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + StopCommand stopCommand = mock(StopCommand.class); + + virtualMachineManagerImpl.updateStopCommandForExternalHypervisorType(HypervisorType.KVM, vmProfile, stopCommand); + + verify(stopCommand, never()).setExternalDetails(any()); + } + + @Test + public void updateStopCommandForExternalHypervisorType_externalHypervisor_setsExternalDetails() { + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + StopCommand stopCommand = mock(StopCommand.class); + HostVO host = mock(HostVO.class); + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + when(vmProfile.getHostId()).thenReturn(1L); + when(hostDaoMock.findById(1L)).thenReturn(host); + when(stopCommand.getVirtualMachine()).thenReturn(vmTO); + when(vmTO.getExternalDetails()).thenReturn(new HashMap<>()); + when(extensionsManager.getExternalAccessDetails(eq(host), any())).thenReturn(new HashMap<>()); + doReturn(mock(VirtualMachineTO.class)).when(virtualMachineManagerImpl).toVmTO(any()); + virtualMachineManagerImpl.updateStopCommandForExternalHypervisorType(HypervisorType.External, vmProfile, stopCommand); + verify(stopCommand).setExternalDetails(any()); + } + + @Test + public void updateRebootCommandWithExternalDetails_nonExternalHypervisor_noAction() { + Host host = mock(Host.class); + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + RebootCommand rebootCmd = mock(RebootCommand.class); + when(host.getHypervisorType()).thenReturn(HypervisorType.KVM); + virtualMachineManagerImpl.updateRebootCommandWithExternalDetails(host, vmTO, rebootCmd); + verify(rebootCmd, never()).setExternalDetails(any()); + } + + @Test + public void updateRebootCommandWithExternalDetails_externalHypervisor_setsExternalDetails() { + Host host = mock(Host.class); + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + RebootCommand rebootCmd = mock(RebootCommand.class); + when(host.getHypervisorType()).thenReturn(HypervisorType.External); + when(vmTO.getExternalDetails()).thenReturn(new HashMap<>()); + when(extensionsManager.getExternalAccessDetails(eq(host), any())).thenReturn(new HashMap<>()); + virtualMachineManagerImpl.updateRebootCommandWithExternalDetails(host, vmTO, rebootCmd); + verify(rebootCmd).setExternalDetails(any()); + } + + @Test + public void updateExternalVmDetailsFromPrepareAnswer_nullDetails_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + UserVmVO userVmVO = mock(UserVmVO.class); + virtualMachineManagerImpl.updateExternalVmDetailsFromPrepareAnswer(vmTO, userVmVO, null); + verify(vmTO, never()).setDetails(any()); + verify(userVmVO, never()).setDetails(any()); + verify(userVmDaoMock, never()).saveDetails(any()); + } + + @Test + public void updateExternalVmDetailsFromPrepareAnswer_sameDetails_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + UserVmVO userVmVO = mock(UserVmVO.class); + Map details = new HashMap<>(); + when(vmTO.getDetails()).thenReturn(details); + virtualMachineManagerImpl.updateExternalVmDetailsFromPrepareAnswer(vmTO, userVmVO, details); + verify(vmTO, never()).setDetails(any()); + verify(userVmVO, never()).setDetails(any()); + verify(userVmDaoMock, never()).saveDetails(any()); + } + + @Test + public void updateExternalVmDataFromPrepareAnswer_vncPasswordUpdated_updatesPassword() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + UserVmVO userVmVO = mock(UserVmVO.class); + when(updatedTO.getVncPassword()).thenReturn("newPassword"); + when(vmTO.getVncPassword()).thenReturn("oldPassword"); + when(userVmDaoMock.findById(anyLong())).thenReturn(userVmVO); + virtualMachineManagerImpl.updateExternalVmDataFromPrepareAnswer(vmTO, updatedTO); + verify(userVmVO).setVncPassword("newPassword"); + verify(vmTO).setVncPassword("newPassword"); + } + + @Test + public void updateExternalVmNicsFromPrepareAnswer_nullNics_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + when(vmTO.getNics()).thenReturn(null); + virtualMachineManagerImpl.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + verify(_nicsDao, never()).findByUuid(anyString()); + } + + @Test + public void updateExternalVmNicsFromPrepareAnswer_updatesNicsSuccessfully() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + NicTO nicTO = mock(NicTO.class); + NicTO updatedNicTO = mock(NicTO.class); + + when(vmTO.getNics()).thenReturn(new NicTO[]{nicTO}); + when(updatedTO.getNics()).thenReturn(new NicTO[]{updatedNicTO}); + when(nicTO.getNicUuid()).thenReturn("nic-uuid"); + when(nicTO.getMac()).thenReturn("mac-a"); + when(updatedNicTO.getNicUuid()).thenReturn("nic-uuid"); + when(updatedNicTO.getMac()).thenReturn("mac-b"); + when(_nicsDao.findByUuid("nic-uuid")).thenReturn(mock(NicVO.class)); + + virtualMachineManagerImpl.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(_nicsDao).findByUuid("nic-uuid"); + verify(_nicsDao).update(anyLong(), any(NicVO.class)); + } + + @Test + public void updateExternalVmNicsFromPrepareAnswer_noMatchingNicUuid_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + NicTO nicTO = mock(NicTO.class); + NicTO updatedNicTO = mock(NicTO.class); + + when(vmTO.getNics()).thenReturn(new NicTO[]{nicTO}); + when(updatedTO.getNics()).thenReturn(new NicTO[]{updatedNicTO}); + when(nicTO.getNicUuid()).thenReturn("nic-uuid"); + when(updatedNicTO.getNicUuid()).thenReturn("different-uuid"); + + virtualMachineManagerImpl.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(_nicsDao, never()).findByUuid(anyString()); + } + + @Test + public void updateExternalVmNicsFromPrepareAnswer_nullUpdatedNics_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + + when(vmTO.getNics()).thenReturn(new NicTO[]{mock(NicTO.class)}); + when(updatedTO.getNics()).thenReturn(null); + + virtualMachineManagerImpl.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(_nicsDao, never()).findByUuid(anyString()); + } + + @Test + public void updateExternalVmNicsFromPrepareAnswer_nullVmNics_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + + when(vmTO.getNics()).thenReturn(null); + when(updatedTO.getNics()).thenReturn(new NicTO[]{mock(NicTO.class)}); + + virtualMachineManagerImpl.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(_nicsDao, never()).findByUuid(anyString()); + } + + @Test + public void updateExternalVmNicsFromPrepareAnswer_emptyNics_noAction() { + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + VirtualMachineTO updatedTO = mock(VirtualMachineTO.class); + + when(vmTO.getNics()).thenReturn(new NicTO[]{}); + when(updatedTO.getNics()).thenReturn(new NicTO[]{}); + + virtualMachineManagerImpl.updateExternalVmNicsFromPrepareAnswer(vmTO, updatedTO); + + verify(_nicsDao, never()).findByUuid(anyString()); + } + + @Test + public void processPrepareExternalProvisioning_nonExternalHypervisor_noAction() throws OperationTimedoutException, AgentUnavailableException { + Host host = mock(Host.class); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + VirtualMachineTemplate template = mock(VirtualMachineTemplate.class); + when(vmProfile.getTemplate()).thenReturn(template); + when(host.getHypervisorType()).thenReturn(HypervisorType.KVM); + virtualMachineManagerImpl.processPrepareExternalProvisioning(true, host, vmProfile, mock(DataCenter.class)); + verify(agentManagerMock, never()).send(anyLong(), any(Command.class)); + } + + @Test + public void processPrepareExternalProvisioning_externalHypervisor_sendsCommand() throws OperationTimedoutException, AgentUnavailableException { + Host host = mock(Host.class); + VirtualMachineProfile vmProfile = mock(VirtualMachineProfile.class); + VirtualMachineTemplate template = mock(VirtualMachineTemplate.class); + when(vmProfile.getTemplate()).thenReturn(template); + NicTO[] nics = new NicTO[]{mock(NicTO.class)}; + VirtualMachineTO vmTO = mock(VirtualMachineTO.class); + when(vmTO.getNics()).thenReturn(nics); + doReturn(vmTO).when(virtualMachineManagerImpl).toVmTO(vmProfile); + ExtensionDetailsVO detailsVO = mock(ExtensionDetailsVO.class); + when(host.getHypervisorType()).thenReturn(HypervisorType.External); + when(template.getExtensionId()).thenReturn(1L); + when(extensionDetailsDao.findDetail(eq(1L), eq(ApiConstants.ORCHESTRATOR_REQUIRES_PREPARE_VM))).thenReturn(detailsVO); + when(detailsVO.getValue()).thenReturn("true"); + PrepareExternalProvisioningAnswer answer = mock(PrepareExternalProvisioningAnswer.class); + when(answer.getResult()).thenReturn(true); + when(answer.getVirtualMachineTO()).thenReturn(vmTO); + when(agentManagerMock.send(anyLong(), any(Command.class))).thenReturn(answer); + virtualMachineManagerImpl.processPrepareExternalProvisioning(true, host, vmProfile, mock(DataCenter.class)); + verify(agentManagerMock).send(anyLong(), any(Command.class)); + } + + @Test + public void testPrepVMSpecForUnmanageInstance() { + // Arrange + final Long accountId = 1L; + final Long offeringId = 1L; + final Long templateId = 1L; + + // Mock vm + VMInstanceVO vm = Mockito.mock(VMInstanceVO.class); + when(vm.getId()).thenReturn(vmInstanceVoMockId); + when(vm.getAccountId()).thenReturn(accountId); + when(vm.getServiceOfferingId()).thenReturn(offeringId); + when(vm.getTemplateId()).thenReturn(templateId); + when(vm.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(vmInstanceDaoMock.findById(vmInstanceVoMockId)).thenReturn(vm); + + // Mock owner + AccountVO owner = Mockito.mock(AccountVO.class); + when(_entityMgr.findById(Account.class, accountId)).thenReturn(owner); + + ServiceOfferingVO offering = Mockito.mock(ServiceOfferingVO.class); + when(serviceOfferingDaoMock.findById(vmInstanceVoMockId, offeringId)).thenReturn(offering); + + VMTemplateVO template = Mockito.mock(VMTemplateVO.class); + when(_entityMgr.findByIdIncludingRemoved(VirtualMachineTemplate.class, templateId)).thenReturn(template); + + when(hostMock.getClusterId()).thenReturn(clusterMockId); + + // Mock cpuOvercommitRatio and ramOvercommitRatio + ClusterDetailsVO cpuOvercommitRatio = Mockito.mock(ClusterDetailsVO.class); + when(cpuOvercommitRatio.getValue()).thenReturn("1.0"); + when(_clusterDetailsDao.findDetail(clusterMockId, VmDetailConstants.CPU_OVER_COMMIT_RATIO)).thenReturn(cpuOvercommitRatio); + ClusterDetailsVO ramOvercommitRatio = Mockito.mock(ClusterDetailsVO.class); + when(ramOvercommitRatio.getValue()).thenReturn("1.0"); + when(_clusterDetailsDao.findDetail(clusterMockId, VmDetailConstants.MEMORY_OVER_COMMIT_RATIO)).thenReturn(ramOvercommitRatio); + + // Mock NICs + List nics = new ArrayList<>(); + NicVO nic1 = Mockito.mock(NicVO.class); + when(nic1.getDeviceId()).thenReturn(1); + nics.add(nic1); + NicVO nic2 = Mockito.mock(NicVO.class); + when(nic2.getDeviceId()).thenReturn(0); + nics.add(nic2); + when(_nicsDao.listByVmId(vmInstanceVoMockId)).thenReturn(nics); + + Network networkMock = Mockito.mock(Network.class); + when(networkModel.getNetwork(anyLong())).thenReturn(networkMock); + + when(volumeVoMock.getVolumeType()).thenReturn(Volume.Type.ROOT); + when(volumeVoMock.getDeviceId()).thenReturn(0L); + when(volumeVoMock.getPath()).thenReturn("/"); + when(volumeVoMock.getDiskOfferingId()).thenReturn(1L); + when(volumeDaoMock.findUsableVolumesForInstance(vmInstanceVoMockId)).thenReturn(List.of(volumeVoMock)); + + VolumeInfo volumeInfo = mock(VolumeInfo.class); + DataTO dataTO = mock(DataTO.class); + when(volumeInfo.getTO()).thenReturn(dataTO); + when(volumeDataFactoryMock.getVolume(anyLong())).thenReturn(volumeInfo); + when(storageManager.getDiskWithThrottling(any(), any(), anyLong(), anyString(), anyLong(), anyLong())).thenReturn(Mockito.mock(DiskTO.class)); + + Map details = new HashMap<>(); + details.put(VirtualMachineProfile.Param.BootType.getName(), "BIOS"); + details.put(VirtualMachineProfile.Param.BootMode.getName(), "LEGACY"); + details.put(VirtualMachineProfile.Param.UefiFlag.getName(), "Yes"); + when(vmInstanceDetailsDao.listDetailsKeyPairs(anyLong(), anyList())).thenReturn(details); + + com.cloud.hypervisor.HypervisorGuru guru = Mockito.mock(com.cloud.hypervisor.HypervisorGuru.class); + when(_hvGuruMgr.getGuru(HypervisorType.KVM)).thenReturn(guru); + VirtualMachineTO vmTO = new VirtualMachineTO() {}; + when(guru.implement(any(VirtualMachineProfile.class))).thenAnswer((Answer) invocation -> { + VirtualMachineProfile profile = invocation.getArgument(0); + assertEquals("BIOS", profile.getParameter(VirtualMachineProfile.Param.BootType)); + return vmTO; + }); + + // Act + VirtualMachineTO result = virtualMachineManagerImpl.prepVmSpecForUnmanageCmd(vmInstanceVoMockId, hostMockId); + + // Assert + assertNotNull(result); + assertEquals(vmTO, result); + verify(_clusterDetailsDao, times(2)).findDetail(eq(clusterMockId), anyString()); + verify(vmInstanceDetailsDao).listDetailsKeyPairs(anyLong(), anyList()); + } + + @Test + public void testPersistDomainForKvmForRunningVmSuccess() throws AgentUnavailableException, OperationTimedoutException { + when(vmInstanceMock.getState()).thenReturn(VirtualMachine.State.Running); + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + UnmanageInstanceAnswer successAnswer = new UnmanageInstanceAnswer(null, true, "success"); + when(agentManagerMock.send(anyLong(), any(Command.class))).thenReturn(successAnswer); + virtualMachineManagerImpl.persistDomainForKVM(vmInstanceMock, null); + ArgumentCaptor hostIdCaptor = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(UnmanageInstanceCommand.class); + verify(agentManagerMock).send(hostIdCaptor.capture(), commandCaptor.capture()); + assertEquals(hostMockId, hostIdCaptor.getValue().longValue()); + } + + @Test + public void testPersistDomainForKvmForStoppedVmSuccess() throws AgentUnavailableException, OperationTimedoutException { + when(vmInstanceMock.getState()).thenReturn(VirtualMachine.State.Stopped); + VirtualMachineTO vmTO = new VirtualMachineTO() {}; + vmTO.setName(vmName); + doReturn(vmTO).when(virtualMachineManagerImpl).prepVmSpecForUnmanageCmd(vmInstanceVoMockId, 1L); + UnmanageInstanceAnswer successAnswer = new UnmanageInstanceAnswer(null, true, "success"); + when(agentManagerMock.send(anyLong(), any(UnmanageInstanceCommand.class))).thenReturn(successAnswer); + when(virtualMachineManagerImpl.findClusterAndHostIdForVm(vmInstanceMock, false)).thenReturn(new Pair<>(clusterMockId, hostMockId)); + virtualMachineManagerImpl.persistDomainForKVM(vmInstanceMock, null); + ArgumentCaptor hostIdCaptor = ArgumentCaptor.forClass(Long.class); + ArgumentCaptor commandCaptor = ArgumentCaptor.forClass(UnmanageInstanceCommand.class); + verify(agentManagerMock).send(hostIdCaptor.capture(), commandCaptor.capture()); + assertEquals(1L, hostIdCaptor.getValue().longValue()); + UnmanageInstanceCommand sentCommand = commandCaptor.getValue(); + assertNotNull(sentCommand.getVm()); + assertEquals(vmTO, sentCommand.getVm()); + assertEquals(vmName, sentCommand.getInstanceName()); + verify(virtualMachineManagerImpl).prepVmSpecForUnmanageCmd(vmInstanceVoMockId, 1L); + } + + + @Test + public void testPersistDomainForKvmForStoppedVmNoHost() { + when(vmInstanceMock.getState()).thenReturn(VirtualMachine.State.Stopped); + VirtualMachineTO vmTO = new VirtualMachineTO() {}; + vmTO.setName(vmName); + when(virtualMachineManagerImpl.findClusterAndHostIdForVm(vmInstanceMock, false)).thenReturn(new Pair<>(clusterMockId, null)); + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> virtualMachineManagerImpl.persistDomainForKVM(vmInstanceMock, null)); + assertEquals("No available host to persist domain XML for Instance: " + vmName, exception.getMessage()); + } + + @Test + public void testPersistDomainForKvmForRunningVmAgentFailure() throws AgentUnavailableException, OperationTimedoutException { + when(vmInstanceMock.getState()).thenReturn(VirtualMachine.State.Running); + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + UnmanageInstanceAnswer failureAnswer = new UnmanageInstanceAnswer(null, false, "failure"); + when(agentManagerMock.send(anyLong(), any(UnmanageInstanceCommand.class))).thenReturn(failureAnswer); + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> virtualMachineManagerImpl.persistDomainForKVM(vmInstanceMock, null)); + assertEquals("Failed to persist domain XML for Instance: " + vmName + " on host ID: " + hostMockId, exception.getMessage()); + } + + @Test + public void testPersistDomainForKvmAgentUnavailable() throws AgentUnavailableException, OperationTimedoutException { + when(vmInstanceMock.getState()).thenReturn(VirtualMachine.State.Running); + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + doThrow(new AgentUnavailableException("Agent down", hostMockId)).when(agentManagerMock).send(anyLong(), any(UnmanageInstanceCommand.class)); + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> virtualMachineManagerImpl.persistDomainForKVM(vmInstanceMock, null)); + assertEquals("Failed to send command to persist domain XML for Instance: " + vmName + " on host ID: " + hostMockId, exception.getMessage()); + } + + @Test(expected = ConcurrentOperationException.class) + public void testUnmanagePendingHaWork() { + when(vmInstanceDaoMock.findByUuid(vmMockUuid)).thenReturn(vmInstanceMock); + when(_workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, vmInstanceVoMockId)).thenReturn(Collections.emptyList()); + when(_haMgr.hasPendingHaWork(vmInstanceVoMockId)).thenReturn(true); + virtualMachineManagerImpl.unmanage(vmMockUuid, null); + } + + @Test + public void testPersistDomainForKvmOperationTimedOut() throws AgentUnavailableException, OperationTimedoutException { + when(vmInstanceMock.getState()).thenReturn(VirtualMachine.State.Running); + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + doThrow(new OperationTimedoutException(null, hostMockId, 123L, 60, false)).when(agentManagerMock).send(anyLong(), any(UnmanageInstanceCommand.class)); + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, () -> virtualMachineManagerImpl.persistDomainForKVM(vmInstanceMock, null)); + assertEquals("Failed to send command to persist domain XML for Instance: " + vmName + " on host ID: " + hostMockId, exception.getMessage()); + } + + @Test(expected = CloudRuntimeException.class) + public void testUnmanageVmRemoved() { + when(vmInstanceMock.getRemoved()).thenReturn(new Date()); + when(vmInstanceDaoMock.findByUuid(vmMockUuid)).thenReturn(vmInstanceMock); + virtualMachineManagerImpl.unmanage(vmMockUuid, null); + } + + @Test(expected = ConcurrentOperationException.class) + public void testUnmanagePendingWorkJobs() { + when(vmInstanceDaoMock.findByUuid(vmMockUuid)).thenReturn(vmInstanceMock); + List pendingJobs = new ArrayList<>(); + VmWorkJobVO vmWorkJobVO = mock(VmWorkJobVO.class); + pendingJobs.add(vmWorkJobVO); + when(_workJobDao.listPendingWorkJobs(VirtualMachine.Type.Instance, vmInstanceVoMockId)).thenReturn(pendingJobs); + virtualMachineManagerImpl.unmanage(vmMockUuid, null); + } + + @Test + public void testUnmanageHostNotFoundAfterTransaction() { + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + when(vmInstanceDaoMock.findByUuid(vmMockUuid)).thenReturn(vmInstanceMock); + when(_workJobDao.listPendingWorkJobs(any(), anyLong())).thenReturn(Collections.emptyList()); + when(_haMgr.hasPendingHaWork(anyLong())).thenReturn(false); + doReturn(guru).when(virtualMachineManagerImpl).getVmGuru(vmInstanceMock); + doNothing().when(virtualMachineManagerImpl).unmanageVMSnapshots(vmInstanceMock); + doNothing().when(virtualMachineManagerImpl).unmanageVMNics(any(VirtualMachineProfile.class), any(VMInstanceVO.class)); + doNothing().when(virtualMachineManagerImpl).unmanageVMVolumes(vmInstanceMock); + doNothing().when(guru).finalizeUnmanage(vmInstanceMock); + try (MockedStatic ignored = Mockito.mockStatic(ApiDBUtils.class)) { + when(ApiDBUtils.findHostById(hostMockId)).thenReturn(null); + Pair result = virtualMachineManagerImpl.unmanage(vmMockUuid, null); + assertNull(result.second()); + } + } + + @Test + public void testUnmanageSuccessNonKvm() { + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + when(hostMock.getUuid()).thenReturn(hostUuid); + when(vmInstanceDaoMock.findByUuid(vmMockUuid)).thenReturn(vmInstanceMock); + when(_workJobDao.listPendingWorkJobs(any(), anyLong())).thenReturn(Collections.emptyList()); + when(_haMgr.hasPendingHaWork(anyLong())).thenReturn(false); + doReturn(guru).when(virtualMachineManagerImpl).getVmGuru(vmInstanceMock); + doNothing().when(virtualMachineManagerImpl).unmanageVMSnapshots(vmInstanceMock); + doNothing().when(virtualMachineManagerImpl).unmanageVMNics(any(VirtualMachineProfile.class), any(VMInstanceVO.class)); + doNothing().when(virtualMachineManagerImpl).unmanageVMVolumes(vmInstanceMock); + doNothing().when(guru).finalizeUnmanage(vmInstanceMock); + try (MockedStatic ignored = Mockito.mockStatic(ApiDBUtils.class)) { + when(ApiDBUtils.findHostById(hostMockId)).thenReturn(hostMock); + try (MockedStatic actionUtil = Mockito.mockStatic(ActionEventUtils.class)) { + actionUtil.when(() -> ActionEventUtils.onActionEvent( + anyLong(), anyLong(), anyLong(), + anyString(), anyString(), + anyLong(), anyString() + )).thenReturn(1L); + + Pair result = virtualMachineManagerImpl.unmanage(vmMockUuid, null); + assertNotNull(result); + assertTrue(result.first()); + assertEquals(hostUuid, result.second()); + + verify(virtualMachineManagerImpl, never()).persistDomainForKVM(any(VMInstanceVO.class), anyLong()); + verify(virtualMachineManagerImpl, times(1)).unmanageVMSnapshots(vmInstanceMock); + verify(virtualMachineManagerImpl, times(1)).unmanageVMNics(any(VirtualMachineProfile.class), any(VMInstanceVO.class)); + verify(virtualMachineManagerImpl, times(1)).unmanageVMVolumes(vmInstanceMock); + verify(guru, times(1)).finalizeUnmanage(vmInstanceMock); + } + } + } + + @Test + public void testUnmanageSuccessKvm() throws Exception { + when(vmInstanceMock.getHostId()).thenReturn(hostMockId); + when(hostMock.getUuid()).thenReturn(hostUuid); + when(vmInstanceMock.getHypervisorType()).thenReturn(HypervisorType.KVM); + when(vmInstanceDaoMock.findByUuid(vmMockUuid)).thenReturn(vmInstanceMock); + when(_workJobDao.listPendingWorkJobs(any(), anyLong())).thenReturn(Collections.emptyList()); + when(_haMgr.hasPendingHaWork(anyLong())).thenReturn(false); + doReturn(guru).when(virtualMachineManagerImpl).getVmGuru(vmInstanceMock); + doNothing().when(virtualMachineManagerImpl).unmanageVMSnapshots(vmInstanceMock); + doNothing().when(virtualMachineManagerImpl).unmanageVMNics(any(VirtualMachineProfile.class), any(VMInstanceVO.class)); + doNothing().when(virtualMachineManagerImpl).unmanageVMVolumes(vmInstanceMock); + doNothing().when(guru).finalizeUnmanage(vmInstanceMock); + try (MockedStatic ignored = Mockito.mockStatic(ApiDBUtils.class)) { + when(ApiDBUtils.findHostById(hostMockId)).thenReturn(hostMock); + try (MockedStatic actionUtil = Mockito.mockStatic(ActionEventUtils.class)) { + actionUtil.when(() -> ActionEventUtils.onActionEvent( + anyLong(), anyLong(), anyLong(), + anyString(), anyString(), + anyLong(), anyString() + )).thenReturn(1L); + UnmanageInstanceAnswer successAnswer = new UnmanageInstanceAnswer(null, true, "success"); + when(agentManagerMock.send(anyLong(), any(UnmanageInstanceCommand.class))).thenReturn(successAnswer); + Pair result = virtualMachineManagerImpl.unmanage(vmMockUuid, null); + assertNotNull(result); + assertTrue(result.first()); + assertEquals(hostUuid, result.second()); + verify(virtualMachineManagerImpl, times(1)).persistDomainForKVM(vmInstanceMock, null); + verify(virtualMachineManagerImpl, times(1)).unmanageVMSnapshots(vmInstanceMock); + verify(virtualMachineManagerImpl, times(1)).unmanageVMNics(any(VirtualMachineProfile.class), any(VMInstanceVO.class)); + verify(virtualMachineManagerImpl, times(1)).unmanageVMVolumes(vmInstanceMock); + verify(guru, times(1)).finalizeUnmanage(vmInstanceMock); + } + } + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_WithClvmVolumes() throws Exception { + long vmId = 100L; + long destHostId = 2L; + long poolId = 10L; + + VolumeVO clvmVolume1 = mock(VolumeVO.class); + VolumeVO clvmVolume2 = mock(VolumeVO.class); + + when(clvmVolume1.getId()).thenReturn(1L); + when(clvmVolume1.getPoolId()).thenReturn(poolId); + when(clvmVolume2.getId()).thenReturn(2L); + when(clvmVolume2.getPoolId()).thenReturn(poolId); + + StoragePoolVO clvmPool = mock(StoragePoolVO.class); + when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(clvmVolume1, clvmVolume2)); + when(storagePoolDaoMock.findById(poolId)).thenReturn(clvmPool); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(1L, destHostId); + verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(2L, destHostId); + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_WithNonClvmVolumes() throws Exception { + long vmId = 100L; + long destHostId = 2L; + long poolId = 10L; + + VolumeVO nfsVolume = mock(VolumeVO.class); + when(nfsVolume.getPoolId()).thenReturn(poolId); + + StoragePoolVO nfsPool = mock(StoragePoolVO.class); + when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(nfsVolume)); + when(storagePoolDaoMock.findById(poolId)).thenReturn(nfsPool); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong()); + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_WithMixedVolumes() throws Exception { + long vmId = 100L; + long destHostId = 2L; + long clvmPoolId = 10L; + long nfsPoolId = 20L; + + VolumeVO clvmVolume = mock(VolumeVO.class); + VolumeVO nfsVolume = mock(VolumeVO.class); + + when(clvmVolume.getId()).thenReturn(1L); + when(clvmVolume.getPoolId()).thenReturn(clvmPoolId); + when(nfsVolume.getPoolId()).thenReturn(nfsPoolId); + + StoragePoolVO clvmPool = mock(StoragePoolVO.class); + when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + StoragePoolVO nfsPool = mock(StoragePoolVO.class); + when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(clvmVolume, nfsVolume)); + when(storagePoolDaoMock.findById(clvmPoolId)).thenReturn(clvmPool); + when(storagePoolDaoMock.findById(nfsPoolId)).thenReturn(nfsPool); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(1L, destHostId); + verify(clvmPoolManagerMock, never()).setClvmLockHostId(2L, destHostId); + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_WithNoVolumes() throws Exception { + long vmId = 100L; + long destHostId = 2L; + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Collections.emptyList()); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong()); + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_WithNullPoolId() throws Exception { + long vmId = 100L; + long destHostId = 2L; + + VolumeVO volumeWithoutPool = mock(VolumeVO.class); + when(volumeWithoutPool.getPoolId()).thenReturn(null); + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(volumeWithoutPool)); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(storagePoolDaoMock, never()).findById(anyLong()); + verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong()); + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_WithNullPool() throws Exception { + long vmId = 100L; + long destHostId = 2L; + long poolId = 10L; + + VolumeVO volume = mock(VolumeVO.class); + when(volume.getPoolId()).thenReturn(poolId); + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(volume)); + when(storagePoolDaoMock.findById(poolId)).thenReturn(null); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(clvmPoolManagerMock, never()).setClvmLockHostId(anyLong(), anyLong()); + } + + @Test + public void testUpdateClvmLockHostForVmVolumes_MultipleClvmPools() throws Exception { + long vmId = 100L; + long destHostId = 2L; + long pool1Id = 10L; + long pool2Id = 20L; + + VolumeVO volume1 = mock(VolumeVO.class); + VolumeVO volume2 = mock(VolumeVO.class); + VolumeVO volume3 = mock(VolumeVO.class); + + when(volume1.getId()).thenReturn(1L); + when(volume1.getPoolId()).thenReturn(pool1Id); + when(volume2.getId()).thenReturn(2L); + when(volume2.getPoolId()).thenReturn(pool2Id); + when(volume3.getId()).thenReturn(3L); + when(volume3.getPoolId()).thenReturn(pool1Id); + + StoragePoolVO clvmPool1 = mock(StoragePoolVO.class); + when(clvmPool1.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + StoragePoolVO clvmPool2 = mock(StoragePoolVO.class); + when(clvmPool2.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + when(volumeDaoMock.findByInstance(vmId)).thenReturn(Arrays.asList(volume1, volume2, volume3)); + when(storagePoolDaoMock.findById(pool1Id)).thenReturn(clvmPool1); + when(storagePoolDaoMock.findById(pool2Id)).thenReturn(clvmPool2); + + ClvmPoolManager clvmPoolManagerMock = injectMockedClvmPoolManager(); + + Method method = getUpdateClvmLockHostForVmVolumesMethod(); + method.invoke(virtualMachineManagerImpl, vmId, destHostId); + + verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(1L, destHostId); + verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(2L, destHostId); + verify(clvmPoolManagerMock, times(1)).setClvmLockHostId(3L, destHostId); + } + } diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java index e3989737112d..66f5b699cc46 100644 --- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/NetworkOrchestratorTest.java @@ -27,6 +27,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -35,11 +36,15 @@ import com.cloud.exception.InsufficientVirtualNetworkCapacityException; import com.cloud.network.IpAddressManager; import com.cloud.utils.Pair; +import org.apache.cloudstack.extension.Extension; +import org.apache.cloudstack.extension.ExtensionHelper; +import org.apache.cloudstack.framework.extensions.network.NetworkExtensionElement; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import org.springframework.test.util.ReflectionTestUtils; import org.mockito.ArgumentMatchers; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -68,6 +73,7 @@ import com.cloud.network.dao.PhysicalNetworkVO; import com.cloud.network.dao.RouterNetworkDao; import com.cloud.network.element.DhcpServiceProvider; +import com.cloud.network.element.NetworkElement; import com.cloud.network.guru.GuestNetworkGuru; import com.cloud.network.guru.NetworkGuru; import com.cloud.network.vpc.VpcManager; @@ -105,6 +111,7 @@ public class NetworkOrchestratorTest extends TestCase { private String guruName = "GuestNetworkGuru"; private String dhcpProvider = "VirtualRouter"; private NetworkGuru guru = mock(NetworkGuru.class); + private NetworkExtensionElement networkExtensionElement; NetworkOfferingVO networkOffering = mock(NetworkOfferingVO.class); @@ -135,6 +142,9 @@ public void setUp() { testOrchestrator.routerJoinDao = mock(DomainRouterJoinDao.class); testOrchestrator._ipAddrMgr = mock(IpAddressManager.class); testOrchestrator._entityMgr = mock(EntityManager.class); + testOrchestrator.extensionHelper = mock(ExtensionHelper.class); + networkExtensionElement = mock(NetworkExtensionElement.class); + ReflectionTestUtils.setField(testOrchestrator, "networkExtensionElement", networkExtensionElement); DhcpServiceProvider provider = mock(DhcpServiceProvider.class); Map capabilities = new HashMap(); @@ -388,7 +398,7 @@ private void configureTestConfigureNicProfileBasedOnRequestedIpTests(NicProfile when(testOrchestrator._ipAddressDao.findByIpAndSourceNetworkId(Mockito.anyLong(), Mockito.anyString())).thenReturn(ipVoSpy); } - VlanVO vlanSpy = Mockito.spy(new VlanVO(Vlan.VlanType.DirectAttached, "vlanTag", vlanGateway, vlanNetmask, 0l, "192.168.100.100 - 192.168.100.200", 0l, new Long(0l), + VlanVO vlanSpy = Mockito.spy(new VlanVO(Vlan.VlanType.DirectAttached, "vlanTag", vlanGateway, vlanNetmask, 0l, "192.168.100.100 - 192.168.100.200", 0l, 0l, "ip6Gateway", "ip6Cidr", "ip6Range")); Mockito.doReturn(0l).when(vlanSpy).getId(); @@ -1010,4 +1020,57 @@ public void testImportNicWithIP4Address() throws Exception { assertEquals("testtag", nicProfile.getName()); } } + + // ----------------------------------------------------------------------- + // Tests for getNetworkElementsIncludingExtensions + // ----------------------------------------------------------------------- + + @Test + public void getNetworkElementsIncludingExtensionsReturnsBaseListWhenNoExtensions() { + when(testOrchestrator.extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator)) + .thenReturn(Collections.emptyList()); + + DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class); + List elements = new ArrayList<>(List.of(dhcpProvider)); + testOrchestrator.networkElements = elements; + + List result = testOrchestrator.getNetworkElementsIncludingExtensions(); + + assertNotNull(result); + assertEquals(elements.size(), result.size()); + } + + @Test + public void getNetworkElementsIncludingExtensionsAddsExtensionElements() { + Extension ext = mock(Extension.class); + when(ext.getName()).thenReturn("my-net-ext"); + when(testOrchestrator.extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator)) + .thenReturn(List.of(ext)); + + NetworkExtensionElement extElement = mock(NetworkExtensionElement.class); + when(networkExtensionElement.withProviderName("my-net-ext")).thenReturn(extElement); + + DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class); + testOrchestrator.networkElements = new ArrayList<>(List.of(dhcpProvider)); + + List result = testOrchestrator.getNetworkElementsIncludingExtensions(); + + assertNotNull(result); + assertEquals(2, result.size()); + assertTrue(result.contains(extElement)); + } + + @Test + public void getNetworkElementsIncludingExtensionsReturnsBaseListWhenExtensionHelperReturnsNull() { + when(testOrchestrator.extensionHelper.listExtensionsByType(Extension.Type.NetworkOrchestrator)) + .thenReturn(null); + + DhcpServiceProvider dhcpProvider = mock(DhcpServiceProvider.class); + testOrchestrator.networkElements = new ArrayList<>(List.of(dhcpProvider)); + + List result = testOrchestrator.getNetworkElementsIncludingExtensions(); + + assertNotNull(result); + assertEquals(1, result.size()); + } } diff --git a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java index a37845c86c2f..d61caf16ae5d 100644 --- a/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java +++ b/engine/orchestration/src/test/java/org/apache/cloudstack/engine/orchestration/VolumeOrchestratorTest.java @@ -16,15 +16,38 @@ // under the License. package org.apache.cloudstack.engine.orchestration; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Date; +import java.util.List; +import java.util.Set; +import java.lang.reflect.Field; +import com.cloud.configuration.Resource; +import com.cloud.deploy.DeploymentClusterPlanner; +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.exception.StorageAccessException; +import com.cloud.host.Host; +import com.cloud.host.HostVO; import com.cloud.hypervisor.Hypervisor; import com.cloud.offering.DiskOffering; +import com.cloud.storage.clvm.ClvmPoolManager; +import com.cloud.storage.ScopeType; +import com.cloud.storage.DataStoreRole; import com.cloud.storage.Storage; import com.cloud.storage.Volume; +import com.cloud.storage.Volume.Type; +import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.VolumeDao; import com.cloud.user.Account; +import com.cloud.user.ResourceLimitService; +import com.cloud.uservm.UserVm; +import com.cloud.utils.db.EntityManager; +import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VMInstanceVO; +import com.cloud.vm.VirtualMachine; +import com.cloud.utils.Pair; import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; import org.apache.cloudstack.engine.subsystem.api.storage.PrimaryDataStore; @@ -32,11 +55,23 @@ import org.apache.cloudstack.engine.subsystem.api.storage.VolumeDataFactory; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; import org.apache.cloudstack.engine.subsystem.api.storage.VolumeService; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.SnapshotDataStoreVO; +import org.apache.cloudstack.framework.config.ConfigDepot; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.secret.PassphraseVO; +import org.apache.cloudstack.secret.dao.PassphraseDao; +import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; import org.apache.commons.lang3.ObjectUtils; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentMatchers; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.MockedConstruction; @@ -45,14 +80,9 @@ import org.mockito.junit.MockitoJUnitRunner; import org.mockito.stubbing.Answer; -import com.cloud.configuration.Resource; -import com.cloud.exception.StorageAccessException; -import com.cloud.host.Host; -import com.cloud.host.HostVO; -import com.cloud.storage.VolumeVO; -import com.cloud.storage.Volume.Type; -import com.cloud.user.ResourceLimitService; -import com.cloud.utils.exception.CloudRuntimeException; +import static org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService.VolumeAllocationAlgorithm; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; @RunWith(MockitoJUnitRunner.class) public class VolumeOrchestratorTest { @@ -65,6 +95,24 @@ public class VolumeOrchestratorTest { protected VolumeDataFactory volumeDataFactory; @Mock protected VolumeDao volumeDao; + @Mock + protected PassphraseDao passphraseDao; + @Mock + protected PrimaryDataStoreDao storagePoolDao; + @Mock + protected EntityManager entityMgr; + @Mock + ConfigDepot configDepot; + @Mock + ConfigurationDao configurationDao; + + + @Mock + private SnapshotDataStoreDao snapshotDataStoreDaoMock; + + @Mock + private ImageStoreDao imageStoreDaoMock; + @Spy @InjectMocks @@ -72,6 +120,9 @@ public class VolumeOrchestratorTest { private static final Long DEFAULT_ACCOUNT_PS_RESOURCE_COUNT = 100L; private Long accountPSResourceCount; + private static final long MOCK_VM_ID = 202L; + private static final long MOCK_POOL_ID = 303L; + private static final String MOCK_VM_NAME = "Test-VM"; @Before public void setUp() throws Exception { @@ -195,7 +246,7 @@ public void testImportVolume() { volumeOrchestrator.importVolume(volumeType, name, diskOffering, sizeInBytes, null, null, zoneId, hypervisorType, null, null, owner, - deviceId, poolId, path, chainInfo); + deviceId, poolId, Storage.StoragePoolType.NetworkFilesystem, path, chainInfo); VolumeVO volume = volumeVOMockedConstructionConstruction.constructed().get(0); Mockito.verify(volume, Mockito.never()).setInstanceId(Mockito.anyLong()); @@ -208,4 +259,690 @@ public void testImportVolume() { Mockito.verify(volume, Mockito.times(1)).setChainInfo(chainInfo); Mockito.verify(volume, Mockito.times(1)).setState(Volume.State.Ready); } + + @Test + public void testAllocateDuplicateVolumeVOBasic() { + Volume oldVol = Mockito.mock(Volume.class); + Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT); + Mockito.when(oldVol.getName()).thenReturn("testVol"); + Mockito.when(oldVol.getDataCenterId()).thenReturn(1L); + Mockito.when(oldVol.getDomainId()).thenReturn(2L); + Mockito.when(oldVol.getAccountId()).thenReturn(3L); + Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L); + Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.THIN); + Mockito.when(oldVol.getSize()).thenReturn(10L); + Mockito.when(oldVol.getMinIops()).thenReturn(100L); + Mockito.when(oldVol.getMaxIops()).thenReturn(200L); + Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.test"); + Mockito.when(oldVol.getTemplateId()).thenReturn(5L); + Mockito.when(oldVol.getDeviceId()).thenReturn(1L); + Mockito.when(oldVol.getInstanceId()).thenReturn(6L); + Mockito.when(oldVol.isRecreatable()).thenReturn(false); + Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.QCOW2); + Mockito.when(oldVol.getPassphraseId()).thenReturn(null); // no encryption + Mockito.when(oldVol.getKmsKeyId()).thenReturn(null); // no encryption + + VolumeVO persistedVol = Mockito.mock(VolumeVO.class); + Mockito.when(volumeDao.persist(Mockito.any(VolumeVO.class))).thenReturn(persistedVol); + + VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, null); + assertNotNull(result); + Mockito.verify(volumeDao, Mockito.times(1)).persist(Mockito.any(VolumeVO.class)); + } + + @Test + public void testAllocateDuplicateVolumeVOWithEncryption() { + Volume oldVol = Mockito.mock(Volume.class); + Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT); + Mockito.when(oldVol.getName()).thenReturn("secureVol"); + Mockito.when(oldVol.getDataCenterId()).thenReturn(1L); + Mockito.when(oldVol.getDomainId()).thenReturn(2L); + Mockito.when(oldVol.getAccountId()).thenReturn(3L); + Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L); + Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.THIN); + Mockito.when(oldVol.getSize()).thenReturn(10L); + Mockito.when(oldVol.getMinIops()).thenReturn(100L); + Mockito.when(oldVol.getMaxIops()).thenReturn(200L); + Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.secure"); + Mockito.when(oldVol.getTemplateId()).thenReturn(5L); + Mockito.when(oldVol.getDeviceId()).thenReturn(2L); + Mockito.when(oldVol.getInstanceId()).thenReturn(7L); + Mockito.when(oldVol.isRecreatable()).thenReturn(true); + Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.RAW); + Mockito.when(oldVol.getKmsKeyId()).thenReturn(null); + Mockito.when(oldVol.getPassphraseId()).thenReturn(42L); + + PassphraseVO passphrase = Mockito.mock(PassphraseVO.class); + Mockito.when(passphrase.getId()).thenReturn(999L); + Mockito.when(passphraseDao.persist(Mockito.any())).thenReturn(passphrase); + + VolumeVO persistedVol = Mockito.mock(VolumeVO.class); + Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol); + + VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, null); + assertNotNull(result); + Mockito.verify(passphraseDao).persist(Mockito.any(PassphraseVO.class)); + Mockito.verify(volumeDao).persist(Mockito.any()); + } + + @Test + public void testAllocateDuplicateVolumeVOWithTemplateOverride() { + Volume oldVol = Mockito.mock(Volume.class); + Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT); + Mockito.when(oldVol.getName()).thenReturn("tmplVol"); + Mockito.when(oldVol.getDataCenterId()).thenReturn(1L); + Mockito.when(oldVol.getDomainId()).thenReturn(2L); + Mockito.when(oldVol.getAccountId()).thenReturn(3L); + Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L); + Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.THIN); + Mockito.when(oldVol.getSize()).thenReturn(20L); + Mockito.when(oldVol.getMinIops()).thenReturn(50L); + Mockito.when(oldVol.getMaxIops()).thenReturn(250L); + Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.tmpl"); + + VolumeVO persistedVol = Mockito.mock(VolumeVO.class); + Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol); + + VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, null, 222L); + assertNotNull(result); + } + + @Test + public void testAllocateDuplicateVolumeVOEncryptionFromOldVolumeOnly() { + Volume oldVol = Mockito.mock(Volume.class); + Mockito.when(oldVol.getVolumeType()).thenReturn(Volume.Type.ROOT); + Mockito.when(oldVol.getName()).thenReturn("vol-old"); + Mockito.when(oldVol.getDataCenterId()).thenReturn(1L); + Mockito.when(oldVol.getDomainId()).thenReturn(2L); + Mockito.when(oldVol.getAccountId()).thenReturn(3L); + Mockito.when(oldVol.getDiskOfferingId()).thenReturn(4L); + Mockito.when(oldVol.getProvisioningType()).thenReturn(Storage.ProvisioningType.SPARSE); + Mockito.when(oldVol.getSize()).thenReturn(30L); + Mockito.when(oldVol.getMinIops()).thenReturn(10L); + Mockito.when(oldVol.getMaxIops()).thenReturn(500L); + Mockito.when(oldVol.get_iScsiName()).thenReturn("iqn.old"); + Mockito.when(oldVol.getTemplateId()).thenReturn(123L); + Mockito.when(oldVol.getDeviceId()).thenReturn(1L); + Mockito.when(oldVol.getInstanceId()).thenReturn(100L); + Mockito.when(oldVol.isRecreatable()).thenReturn(false); + Mockito.when(oldVol.getFormat()).thenReturn(Storage.ImageFormat.RAW); + + DiskOffering diskOffering = Mockito.mock(DiskOffering.class); + Mockito.when(diskOffering.getEncrypt()).thenReturn(false); // explicitly disables encryption + + VolumeVO persistedVol = Mockito.mock(VolumeVO.class); + Mockito.when(volumeDao.persist(Mockito.any())).thenReturn(persistedVol); + + VolumeVO result = volumeOrchestrator.allocateDuplicateVolumeVO(oldVol, diskOffering, null); + assertNotNull(result); + Mockito.verify(volumeDao).persist(Mockito.any()); + } + + @Test + public void testVolumeOnSharedStoragePoolTrue() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getPoolId()).thenReturn(MOCK_POOL_ID); + + StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool.getScope()).thenReturn(ScopeType.CLUSTER); // Shared scope + Mockito.when(storagePoolDao.findById(MOCK_POOL_ID)).thenReturn(pool); + + assertTrue(volumeOrchestrator.volumeOnSharedStoragePool(volume)); + } + + @Test + public void testVolumeOnSharedStoragePoolFalseHostScope() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getPoolId()).thenReturn(MOCK_POOL_ID); + + StoragePoolVO pool = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool.getScope()).thenReturn(ScopeType.HOST); // Local scope + Mockito.when(storagePoolDao.findById(MOCK_POOL_ID)).thenReturn(pool); + + Assert.assertFalse(volumeOrchestrator.volumeOnSharedStoragePool(volume)); + } + + @Test + public void testVolumeOnSharedStoragePoolFalseNoPool() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getPoolId()).thenReturn(null); // No pool associated + + Assert.assertFalse(volumeOrchestrator.volumeOnSharedStoragePool(volume)); + Mockito.verify(storagePoolDao, Mockito.never()).findById(Mockito.anyLong()); + } + + @Test + public void testVolumeOnSharedStoragePoolFalsePoolNotFound() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getPoolId()).thenReturn(MOCK_POOL_ID); + + Mockito.when(storagePoolDao.findById(MOCK_POOL_ID)).thenReturn(null); // Pool not found in DB + + Assert.assertFalse(volumeOrchestrator.volumeOnSharedStoragePool(volume)); + } + + + @Test + public void testVolumeInactiveNoVmId() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(null); + assertTrue(volumeOrchestrator.volumeInactive(volume)); + Mockito.verify(entityMgr, Mockito.never()).findById(Mockito.eq(UserVm.class), Mockito.anyLong()); + } + + @Test + public void testVolumeInactiveVmNotFound() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID); + Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(null); + assertTrue(volumeOrchestrator.volumeInactive(volume)); + } + + @Test + public void testVolumeInactiveVmStopped() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID); + UserVm vm = Mockito.mock(UserVm.class); + Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Stopped); + Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(vm); + assertTrue(volumeOrchestrator.volumeInactive(volume)); + } + + @Test + public void testVolumeInactiveVmDestroyed() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID); + UserVm vm = Mockito.mock(UserVm.class); + Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Destroyed); + Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(vm); + assertTrue(volumeOrchestrator.volumeInactive(volume)); + } + + @Test + public void testVolumeInactiveVmRunning() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID); + UserVm vm = Mockito.mock(UserVm.class); + Mockito.when(vm.getState()).thenReturn(VirtualMachine.State.Running); // Active state + Mockito.when(entityMgr.findById(UserVm.class, MOCK_VM_ID)).thenReturn(vm); + Assert.assertFalse(volumeOrchestrator.volumeInactive(volume)); + } + + @Test + public void testGetVmNameOnVolumeNoVmId() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(null); + Assert.assertNull(volumeOrchestrator.getVmNameOnVolume(volume)); + Mockito.verify(entityMgr, Mockito.never()).findById(Mockito.eq(VirtualMachine.class), Mockito.anyLong()); + } + + @Test + public void testGetVmNameOnVolumeVmNotFound() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID); + Mockito.when(entityMgr.findById(VirtualMachine.class, MOCK_VM_ID)).thenReturn(null); + Assert.assertNull(volumeOrchestrator.getVmNameOnVolume(volume)); + } + + @Test + public void testGetVmNameOnVolumeSuccess() { + VolumeVO volume = Mockito.mock(VolumeVO.class); + Mockito.when(volume.getInstanceId()).thenReturn(MOCK_VM_ID); + VirtualMachine vm = Mockito.mock(VirtualMachine.class); + Mockito.when(vm.getInstanceName()).thenReturn(MOCK_VM_NAME); + Mockito.when(entityMgr.findById(VirtualMachine.class, MOCK_VM_ID)).thenReturn(vm); + Assert.assertEquals(MOCK_VM_NAME, volumeOrchestrator.getVmNameOnVolume(volume)); + } + + @Test + public void testValidateVolumeSizeRangeValid() throws Exception { + overrideDefaultConfigValue(VolumeOrchestrator.MaxVolumeSize, "2000"); + assertTrue(volumeOrchestrator.validateVolumeSizeRange(1024 * 1024 * 1024)); // 1 GiB + assertTrue(volumeOrchestrator.validateVolumeSizeRange(2000 * 1024 * 1024 * 1024)); // 2 TiB + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateVolumeSizeRangeTooSmall() { + volumeOrchestrator.validateVolumeSizeRange(1024L); // Less than 1GiB + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateVolumeSizeRangeNegative() { + volumeOrchestrator.validateVolumeSizeRange(-10); // Negative size + } + + @Test(expected = InvalidParameterValueException.class) + public void testValidateVolumeSizeRangeTooLarge() throws Exception { + overrideDefaultConfigValue(VolumeOrchestrator.MaxVolumeSize, "100L"); + volumeOrchestrator.validateVolumeSizeRange(101); + } + + @Test + public void testCanVmRestartOnAnotherServerAllShared() { + VolumeVO vol1 = Mockito.mock(VolumeVO.class); + VolumeVO vol2 = Mockito.mock(VolumeVO.class); + Mockito.when(vol1.getPoolId()).thenReturn(10L); + Mockito.when(vol2.getPoolId()).thenReturn(20L); + Mockito.when(vol1.isRecreatable()).thenReturn(false); + Mockito.when(vol2.isRecreatable()).thenReturn(false); + + + StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class); + StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); // Shared + Mockito.when(pool2.getPoolType()).thenReturn(Storage.StoragePoolType.RBD); // Shared + + Mockito.when(volumeDao.findCreatedByInstance(MOCK_VM_ID)).thenReturn(List.of(vol1, vol2)); + Mockito.when(storagePoolDao.findById(10L)).thenReturn(pool1); + Mockito.when(storagePoolDao.findById(20L)).thenReturn(pool2); + + + assertTrue(volumeOrchestrator.canVmRestartOnAnotherServer(MOCK_VM_ID)); + } + + @Test + public void testCanVmRestartOnAnotherServerOneLocalNotRecreatable() { + VolumeVO vol1 = Mockito.mock(VolumeVO.class); + VolumeVO vol2 = Mockito.mock(VolumeVO.class); // Local, not recreatable + Mockito.when(vol1.getPoolId()).thenReturn(10L); + Mockito.when(vol2.getPoolId()).thenReturn(30L); + Mockito.when(vol1.isRecreatable()).thenReturn(false); + Mockito.when(vol2.isRecreatable()).thenReturn(false); // Not recreatable + + StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class); + StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); // Shared + Mockito.when(pool2.getPoolType()).thenReturn(Storage.StoragePoolType.LVM); // Local + + Mockito.when(volumeDao.findCreatedByInstance(MOCK_VM_ID)).thenReturn(List.of(vol1, vol2)); + Mockito.when(storagePoolDao.findById(10L)).thenReturn(pool1); + Mockito.when(storagePoolDao.findById(30L)).thenReturn(pool2); + + Assert.assertFalse("VM restart should be false if a non-recreatable local disk exists", + volumeOrchestrator.canVmRestartOnAnotherServer(MOCK_VM_ID)); + } + + @Test + public void testCanVmRestartOnAnotherServerOneLocalRecreatable() { + VolumeVO vol1 = Mockito.mock(VolumeVO.class); + VolumeVO vol2 = Mockito.mock(VolumeVO.class); // Local, but recreatable + Mockito.when(vol1.getPoolId()).thenReturn(10L); + Mockito.when(vol2.getPoolId()).thenReturn(30L); + Mockito.when(vol1.isRecreatable()).thenReturn(false); + Mockito.when(vol2.isRecreatable()).thenReturn(true); // Recreatable + + StoragePoolVO pool1 = Mockito.mock(StoragePoolVO.class); + StoragePoolVO pool2 = Mockito.mock(StoragePoolVO.class); + Mockito.when(pool1.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); // Shared + + Mockito.when(volumeDao.findCreatedByInstance(MOCK_VM_ID)).thenReturn(List.of(vol1, vol2)); + Mockito.when(storagePoolDao.findById(10L)).thenReturn(pool1); + Mockito.when(storagePoolDao.findById(30L)).thenReturn(pool2); + + assertTrue("VM restart should be true if local disk is recreatable", + volumeOrchestrator.canVmRestartOnAnotherServer(MOCK_VM_ID)); + } + + private void overrideDefaultConfigValue(final ConfigKey configKey, final String value) throws IllegalAccessException, NoSuchFieldException { + final Field f = ConfigKey.class.getDeclaredField("_defaultValue"); + f.setAccessible(true); + f.set(configKey, value); + } + + @Test + public void testStart() throws Exception { + Mockito.when(configDepot.isNewConfig(VolumeAllocationAlgorithm)).thenReturn(true); + overrideDefaultConfigValue(DeploymentClusterPlanner.VmAllocationAlgorithm, "firstfit"); + Mockito.when(configurationDao.update(Mockito.anyString(), Mockito.anyString())).thenReturn(true); + volumeOrchestrator.start(); + } + + @Test + public void testConfigKeys() { + assertTrue(volumeOrchestrator.getConfigKeys().length > 0); + } + + @Test + public void getVolumeCheckpointPathsAndImageStoreUrlsTestReturnEmptyListsIfNotKVM() { + Pair, Set> result = volumeOrchestrator.getVolumeCheckpointPathsAndImageStoreUrls(0, Hypervisor.HypervisorType.VMware); + + Assert.assertTrue(result.first().isEmpty()); + Assert.assertTrue(result.second().isEmpty()); + } + + @Test + public void getVolumeCheckpointPathsAndImageStoreUrlsTestReturnCheckpointIfKVM() { + SnapshotDataStoreVO snapshotDataStoreVO = new SnapshotDataStoreVO(); + snapshotDataStoreVO.setKvmCheckpointPath("Test"); + snapshotDataStoreVO.setRole(DataStoreRole.Primary); + + Mockito.doReturn(List.of(snapshotDataStoreVO)).when(snapshotDataStoreDaoMock).listReadyByVolumeIdAndCheckpointPathNotNull(Mockito.anyLong()); + + Pair, Set> result = volumeOrchestrator.getVolumeCheckpointPathsAndImageStoreUrls(0, Hypervisor.HypervisorType.KVM); + + Assert.assertEquals("Test", result.first().get(0)); + Assert.assertTrue(result.second().isEmpty()); + } + + @Test + public void getVolumeCheckpointPathsAndImageStoreUrlsTestReturnCheckpointIfKVMAndImageStore() { + SnapshotDataStoreVO snapshotDataStoreVO = new SnapshotDataStoreVO(); + snapshotDataStoreVO.setKvmCheckpointPath("Test"); + snapshotDataStoreVO.setRole(DataStoreRole.Image); + snapshotDataStoreVO.setDataStoreId(13); + + Mockito.doReturn(List.of(snapshotDataStoreVO)).when(snapshotDataStoreDaoMock).listReadyByVolumeIdAndCheckpointPathNotNull(Mockito.anyLong()); + + ImageStoreVO imageStoreVO = new ImageStoreVO(); + imageStoreVO.setUrl("URL"); + Mockito.doReturn(imageStoreVO).when(imageStoreDaoMock).findById(Mockito.anyLong()); + + Pair, Set> result = volumeOrchestrator.getVolumeCheckpointPathsAndImageStoreUrls(0, Hypervisor.HypervisorType.KVM); + + Assert.assertEquals("Test", result.first().get(0)); + Assert.assertTrue(result.second().contains("URL")); + Assert.assertEquals(1, result.second().size()); + } + + @Test + public void testTransferClvmLocksForVmStart_WithClvmVolumes() throws Exception { + Long destHostId = 2L; + Long currentHostId = 1L; + Long poolId = 10L; + + VolumeVO clvmVolume1 = Mockito.mock(VolumeVO.class); + VolumeVO clvmVolume2 = Mockito.mock(VolumeVO.class); + + Mockito.when(clvmVolume1.getId()).thenReturn(101L); + Mockito.when(clvmVolume1.getPoolId()).thenReturn(poolId); + Mockito.when(clvmVolume1.getUuid()).thenReturn("vol-uuid-1"); + Mockito.when(clvmVolume1.getPath()).thenReturn("vol-path-1"); + + Mockito.when(clvmVolume2.getId()).thenReturn(102L); + Mockito.when(clvmVolume2.getPoolId()).thenReturn(poolId); + Mockito.when(clvmVolume2.getUuid()).thenReturn("vol-uuid-2"); + Mockito.when(clvmVolume2.getPath()).thenReturn("vol-path-2"); + + StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmInstance.getInstanceName()).thenReturn(MOCK_VM_NAME); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), Mockito.anyString(), + Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId); + Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(102L), Mockito.anyString(), + Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId); + Mockito.when(clvmPoolManager.transferClvmVolumeLock(Mockito.anyString(), Mockito.anyLong(), + Mockito.anyString(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(true); + + Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool); + + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, List.of(clvmVolume1, clvmVolume2), destHostId, vmInstance); + + Mockito.verify(clvmPoolManager, Mockito.times(1)).transferClvmVolumeLock( + Mockito.eq("vol-uuid-1"), Mockito.eq(101L), Mockito.eq("vol-path-1"), + Mockito.eq(clvmPool), Mockito.eq(currentHostId), Mockito.eq(destHostId)); + Mockito.verify(clvmPoolManager, Mockito.times(1)).transferClvmVolumeLock( + Mockito.eq("vol-uuid-2"), Mockito.eq(102L), Mockito.eq("vol-path-2"), + Mockito.eq(clvmPool), Mockito.eq(currentHostId), Mockito.eq(destHostId)); + } + + @Test + public void testTransferClvmLocksForVmStart_WithNonClvmVolumes() throws Exception { + Long destHostId = 2L; + Long poolId = 10L; + + VolumeVO nfsVolume = Mockito.mock(VolumeVO.class); + Mockito.when(nfsVolume.getPoolId()).thenReturn(poolId); + + StoragePoolVO nfsPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + + Mockito.when(storagePoolDao.findById(poolId)).thenReturn(nfsPool); + + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, List.of(nfsVolume), destHostId, vmInstance); + + Mockito.verify(clvmPoolManager, Mockito.never()).transferClvmVolumeLock( + Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), + Mockito.any(), Mockito.anyLong(), Mockito.anyLong()); + } + + @Test + public void testTransferClvmLocksForVmStart_NoLockTransferNeeded() throws Exception { + Long destHostId = 2L; + Long poolId = 10L; + + VolumeVO clvmVolume = Mockito.mock(VolumeVO.class); + Mockito.when(clvmVolume.getId()).thenReturn(101L); + Mockito.when(clvmVolume.getPoolId()).thenReturn(poolId); + + StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), ArgumentMatchers.nullable(String.class), + ArgumentMatchers.nullable(String.class), Mockito.any(), Mockito.eq(true))).thenReturn(destHostId); + + Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool); + + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + java.lang.reflect.Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, List.of(clvmVolume), destHostId, vmInstance); + + Mockito.verify(clvmPoolManager, Mockito.never()).transferClvmVolumeLock( + Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), + Mockito.any(), Mockito.anyLong(), Mockito.anyLong()); + } + + @Test + public void testTransferClvmLocksForVmStart_EmptyVolumeList() throws Exception { + Long destHostId = 2L; + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, new ArrayList(), destHostId, vmInstance); + + Mockito.verify(clvmPoolManager, Mockito.never()).getClvmLockHostId(Mockito.anyLong(), Mockito.anyString(), + Mockito.anyString(), Mockito.any(), Mockito.anyBoolean()); + } + + @Test + public void testTransferClvmLocksForVmStart_NullPoolId() throws Exception { + Long destHostId = 2L; + + VolumeVO volumeWithoutPool = Mockito.mock(VolumeVO.class); + Mockito.when(volumeWithoutPool.getPoolId()).thenReturn(null); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, List.of(volumeWithoutPool), destHostId, vmInstance); + + Mockito.verify(storagePoolDao, Mockito.never()).findById(Mockito.anyLong()); + } + + @Test + public void testTransferClvmLocksForVmStart_SetInitialLockHost() throws Exception { + Long destHostId = 2L; + Long poolId = 10L; + + VolumeVO clvmVolume = Mockito.mock(VolumeVO.class); + Mockito.when(clvmVolume.getId()).thenReturn(101L); + Mockito.when(clvmVolume.getPoolId()).thenReturn(poolId); + + StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), ArgumentMatchers.nullable(String.class), + ArgumentMatchers.nullable(String.class), Mockito.any(), Mockito.eq(true))).thenReturn(null); + + Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool); + + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, List.of(clvmVolume), destHostId, vmInstance); + + Mockito.verify(clvmPoolManager, Mockito.times(1)).setClvmLockHostId(101L, destHostId); + Mockito.verify(clvmPoolManager, Mockito.never()).transferClvmVolumeLock( + Mockito.anyString(), Mockito.anyLong(), Mockito.anyString(), + Mockito.any(), Mockito.anyLong(), Mockito.anyLong()); + } + + @Test + public void testTransferClvmLocksForVmStart_MixedVolumes() throws Exception { + Long destHostId = 2L; + Long currentHostId = 1L; + Long clvmPoolId = 10L; + Long nfsPoolId = 20L; + + VolumeVO clvmVolume = Mockito.mock(VolumeVO.class); + Mockito.when(clvmVolume.getId()).thenReturn(101L); + Mockito.when(clvmVolume.getPoolId()).thenReturn(clvmPoolId); + Mockito.when(clvmVolume.getUuid()).thenReturn("clvm-vol-uuid"); + Mockito.when(clvmVolume.getPath()).thenReturn("clvm-vol-path"); + + VolumeVO nfsVolume = Mockito.mock(VolumeVO.class); + Mockito.when(nfsVolume.getPoolId()).thenReturn(nfsPoolId); + + StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + StoragePoolVO nfsPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(nfsPool.getPoolType()).thenReturn(Storage.StoragePoolType.NetworkFilesystem); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmInstance.getInstanceName()).thenReturn(MOCK_VM_NAME); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), Mockito.anyString(), + Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId); + Mockito.when(clvmPoolManager.transferClvmVolumeLock(Mockito.anyString(), Mockito.anyLong(), + Mockito.anyString(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(true); + + Mockito.when(storagePoolDao.findById(clvmPoolId)).thenReturn(clvmPool); + Mockito.when(storagePoolDao.findById(nfsPoolId)).thenReturn(nfsPool); + + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + method.invoke(volumeOrchestrator, List.of(clvmVolume, nfsVolume), destHostId, vmInstance); + + Mockito.verify(clvmPoolManager, Mockito.times(1)).transferClvmVolumeLock( + Mockito.eq("clvm-vol-uuid"), Mockito.eq(101L), Mockito.eq("clvm-vol-path"), + Mockito.eq(clvmPool), Mockito.eq(currentHostId), Mockito.eq(destHostId)); + } + + @Test(expected = CloudRuntimeException.class) + public void testTransferClvmLocksForVmStart_TransferFails() throws Throwable { + Long destHostId = 2L; + Long currentHostId = 1L; + Long poolId = 10L; + + VolumeVO clvmVolume = Mockito.mock(VolumeVO.class); + Mockito.when(clvmVolume.getId()).thenReturn(101L); + Mockito.when(clvmVolume.getPoolId()).thenReturn(poolId); + Mockito.when(clvmVolume.getUuid()).thenReturn("vol-uuid"); + Mockito.when(clvmVolume.getPath()).thenReturn("vol-path"); + + StoragePoolVO clvmPool = Mockito.mock(StoragePoolVO.class); + Mockito.when(clvmPool.getPoolType()).thenReturn(Storage.StoragePoolType.CLVM); + + VMInstanceVO vmInstance = Mockito.mock(VMInstanceVO.class); + Mockito.when(vmInstance.getInstanceName()).thenReturn(MOCK_VM_NAME); + + ClvmPoolManager clvmPoolManager = Mockito.mock(ClvmPoolManager.class); + Mockito.when(clvmPoolManager.getClvmLockHostId(Mockito.eq(101L), Mockito.anyString(), + Mockito.anyString(), Mockito.any(), Mockito.eq(true))).thenReturn(currentHostId); + Mockito.when(clvmPoolManager.transferClvmVolumeLock(Mockito.anyString(), Mockito.anyLong(), + Mockito.anyString(), Mockito.any(), Mockito.anyLong(), Mockito.anyLong())).thenReturn(false); + + Mockito.when(storagePoolDao.findById(poolId)).thenReturn(clvmPool); + + setField(volumeOrchestrator, "clvmPoolManager", clvmPoolManager); + setField(volumeOrchestrator, "_storagePoolDao", storagePoolDao); + + Method method = VolumeOrchestrator.class.getDeclaredMethod( + "transferClvmLocksForVmStart", List.class, Long.class, VMInstanceVO.class); + method.setAccessible(true); + + try { + method.invoke(volumeOrchestrator, List.of(clvmVolume), destHostId, vmInstance); + } catch (InvocationTargetException e) { + throw e.getCause(); + } + } + + private void setField(Object target, String fieldName, Object value) throws Exception { + Field field = findField(target.getClass(), fieldName); + if (field == null) { + throw new NoSuchFieldException("Field " + fieldName + " not found in " + target.getClass()); + } + field.setAccessible(true); + field.set(target, value); + } + + private Field findField(Class clazz, String fieldName) { + Class current = clazz; + while (current != null && current != Object.class) { + try { + return current.getDeclaredField(fieldName); + } catch (NoSuchFieldException e) { + current = current.getSuperclass(); + } + } + return null; + } + } diff --git a/engine/pom.xml b/engine/pom.xml index 2da9515dbf12..2de84eab85af 100644 --- a/engine/pom.xml +++ b/engine/pom.xml @@ -25,7 +25,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml diff --git a/engine/schema/pom.xml b/engine/schema/pom.xml index 0cd4a4011938..664d4909e677 100644 --- a/engine/schema/pom.xml +++ b/engine/schema/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloud-engine - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT ../pom.xml @@ -48,6 +48,11 @@ cloud-framework-db ${project.version}
+ + org.apache.cloudstack + cloud-framework-kms + ${project.version} + com.mysql mysql-connector-j @@ -106,7 +111,7 @@ templateList.add("systemvmtemplate-${csVersion}.${patch}-x86_64-xen") templateList.add("systemvmtemplate-${csVersion}.${patch}-x86_64-ovm") templateList.add("systemvmtemplate-${csVersion}.${patch}-x86_64-hyperv") - File file = new File("./engine/schema/dist/systemvm-templates/md5sum.txt") + File file = new File("./engine/schema/dist/systemvm-templates/sha512sum.txt") def lines = file.readLines() for (template in templateList) { def data = lines.findAll { it.contains(template) } @@ -135,7 +140,7 @@ wget - ${project.systemvm.template.location}/${cs.version}/md5sum.txt + ${project.systemvm.template.location}/${cs.version}/sha512sum.txt ${basedir}/dist/systemvm-templates/ true true @@ -205,7 +210,7 @@ true ${project.systemvm.template.location}/${cs.version}/systemvmtemplate-${cs.version}.${patch.version}-x86_64-kvm.qcow2.bz2 ${basedir}/dist/systemvm-templates/ - ${kvm.checksum} + ${kvm.checksum} @@ -241,7 +246,7 @@ true ${project.systemvm.template.location}/${cs.version}/systemvmtemplate-${cs.version}.${patch.version}-x86_64-vmware.ova ${basedir}/dist/systemvm-templates/ - ${vmware.checksum} + ${vmware.checksum} @@ -277,7 +282,7 @@ true ${project.systemvm.template.location}/${cs.version}/systemvmtemplate-${cs.version}.${patch.version}-x86_64-xen.vhd.bz2 ${basedir}/dist/systemvm-templates/ - ${xen.checksum} + ${xen.checksum} @@ -313,7 +318,7 @@ true ${project.systemvm.template.location}/${cs.version}/systemvmtemplate-${cs.version}.${patch.version}-x86_64-ovm.raw.bz2 ${basedir}/dist/systemvm-templates/ - ${ovm.checksum} + ${ovm.checksum} @@ -349,7 +354,7 @@ true ${project.systemvm.template.location}/${cs.version}/systemvmtemplate-${cs.version}.${patch.version}-x86_64-hyperv.vhd.zip ${basedir}/dist/systemvm-templates/ - ${hyperv.checksum} + ${hyperv.checksum} diff --git a/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java b/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java index 94d01f472ba5..97b7c54f0844 100644 --- a/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/alert/dao/AlertDaoImpl.java @@ -20,6 +20,7 @@ import java.util.List; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.alert.AlertVO; @@ -28,7 +29,7 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; -import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.UpdateBuilder; @Component public class AlertDaoImpl extends GenericDaoBase implements AlertDao { @@ -107,25 +108,20 @@ public boolean archiveAlert(List ids, String type, Date startDate, Date en } sc.setParameters("archived", false); - boolean result = true; - ; List alerts = listBy(sc); if (ids != null && alerts.size() < ids.size()) { - result = false; - return result; + return false; } - if (alerts != null && !alerts.isEmpty()) { - TransactionLegacy txn = TransactionLegacy.currentTxn(); - txn.start(); - for (AlertVO alert : alerts) { - alert = lockRow(alert.getId(), true); - alert.setArchived(true); - update(alert.getId(), alert); - txn.commit(); - } - txn.close(); + + if (CollectionUtils.isEmpty(alerts)) { + return true; } - return result; + + AlertVO alertForUpdate = createForUpdate(); + alertForUpdate.setArchived(true); + UpdateBuilder ub = getUpdateBuilder(alertForUpdate); + update(ub, sc, null); + return true; } @Override diff --git a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java index cd62935f17ee..fb2d61d8e11a 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java +++ b/engine/schema/src/main/java/com/cloud/capacity/CapacityVO.java @@ -254,6 +254,8 @@ public String getUuid() { capacityNames.put(CAPACITY_TYPE_GPU, "GPU"); capacityNames.put(CAPACITY_TYPE_CPU_CORE, "CPU_CORE"); capacityNames.put(CAPACITY_TYPE_VIRTUAL_NETWORK_IPV6_SUBNET, "VIRTUAL_NETWORK_IPV6_SUBNET"); + capacityNames.put(CAPACITY_TYPE_BACKUP_STORAGE, "BACKUP_STORAGE"); + capacityNames.put(CAPACITY_TYPE_OBJECT_STORAGE, "OBJECT_STORAGE"); } public static String getCapacityName (Short capacityType) { diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java index 1bb79ce417aa..42313efa512f 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDao.java @@ -30,7 +30,7 @@ public interface CapacityDao extends GenericDao { List listByHostIdTypes(Long hostId, List capacityTypes); - List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone); + List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, boolean isZone); List listHostsWithEnoughCapacity(int requiredCpu, long requiredRam, Long clusterId, String hostType); @@ -48,7 +48,7 @@ public interface CapacityDao extends GenericDao { List findFilteredCapacityBy(Integer capacityType, Long zoneId, Long podId, Long clusterId, List hostIds, List poolIds); - List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam, short capacityType); + List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam); Pair, Map> orderPodsByAggregateCapacity(long zoneId, short capacityType); @@ -64,5 +64,11 @@ List listCapacitiesGroupedByLevelAndType(Integer capacityType, L float findClusterConsumption(Long clusterId, short capacityType, long computeRequested); - List orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityType); + Pair, Map> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityType); + + List listHostCapacityByCapacityTypes(Long zoneId, Long clusterId, List capacityTypes); + + List listPodCapacityByCapacityTypes(Long zoneId, List capacityTypes); + + List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, List capacityTypes); } diff --git a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java index 7bd3df103b1f..f65c3cf188cd 100644 --- a/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/capacity/dao/CapacityDaoImpl.java @@ -204,9 +204,9 @@ public class CapacityDaoImpl extends GenericDaoBase implements "(CASE WHEN ISNULL(service_offering.speed) THEN custom_speed.value ELSE service_offering.speed end) AS speed, " + "(CASE WHEN ISNULL(service_offering.ram_size) THEN custom_ram_size.value ELSE service_offering.ram_size end) AS ram_size " + "FROM vm_instance vi LEFT JOIN service_offering ON(((vi.service_offering_id = service_offering.id))) " + - "LEFT JOIN user_vm_details custom_cpu ON(((custom_cpu.vm_id = vi.id) AND (custom_cpu.name = 'CpuNumber'))) " + - "LEFT JOIN user_vm_details custom_speed ON(((custom_speed.vm_id = vi.id) AND (custom_speed.name = 'CpuSpeed'))) " + - "LEFT JOIN user_vm_details custom_ram_size ON(((custom_ram_size.vm_id = vi.id) AND (custom_ram_size.name = 'memory'))) "; + "LEFT JOIN vm_instance_details custom_cpu ON(((custom_cpu.vm_id = vi.id) AND (custom_cpu.name = 'CpuNumber'))) " + + "LEFT JOIN vm_instance_details custom_speed ON(((custom_speed.vm_id = vi.id) AND (custom_speed.name = 'CpuSpeed'))) " + + "LEFT JOIN vm_instance_details custom_ram_size ON(((custom_ram_size.vm_id = vi.id) AND (custom_ram_size.name = 'memory'))) "; private static final String WHERE_STATE_IS_NOT_DESTRUCTIVE = "WHERE ISNULL(vi.removed) AND vi.state NOT IN ('Destroyed', 'Error', 'Expunging')"; @@ -695,7 +695,7 @@ public List listByHostIdTypes(Long hostId, List capacityTypes } @Override - public List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, short capacityTypeForOrdering, boolean isZone) { + public List listClustersInZoneOrPodByHostCapacities(long id, long vmId, int requiredCpu, long requiredRam, boolean isZone) { TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; List result = new ArrayList(); @@ -1039,10 +1039,11 @@ public Pair, Map> orderClustersByAggregateCapacity(long } @Override - public List orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityTypeForOrdering){ + public Pair, Map> orderHostsByFreeCapacity(Long zoneId, Long clusterId, short capacityTypeForOrdering){ TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; - List result = new ArrayList(); + List result = new ArrayList<>(); + Map hostCapacityMap = new HashMap<>(); StringBuilder sql = new StringBuilder(ORDER_HOSTS_BY_FREE_CAPACITY_PART1); if (zoneId != null) { sql.append(" AND data_center_id = ?"); @@ -1065,9 +1066,11 @@ public List orderHostsByFreeCapacity(Long zoneId, Long clusterId, short ca ResultSet rs = pstmt.executeQuery(); while (rs.next()) { - result.add(rs.getLong(1)); + Long hostId = rs.getLong(1); + result.add(hostId); + hostCapacityMap.put(hostId, rs.getDouble(2)); } - return result; + return new Pair<>(result, hostCapacityMap); } catch (SQLException e) { throw new CloudRuntimeException("DB Exception on: " + sql, e); } catch (Throwable e) { @@ -1076,7 +1079,65 @@ public List orderHostsByFreeCapacity(Long zoneId, Long clusterId, short ca } @Override - public List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam, short capacityType) { + public List listHostCapacityByCapacityTypes(Long zoneId, Long clusterId, List capacityTypes) { + SearchBuilder sb = createSearchBuilder(); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ); + sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.and("capacityState", sb.entity().getCapacityState(), Op.EQ); + sb.done(); + + SearchCriteria sc = sb.create(); + sc.setParameters("capacityState", "Enabled"); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + if (clusterId != null) { + sc.setParameters("clusterId", clusterId); + } + sc.setParameters("capacityTypes", capacityTypes.toArray()); + return listBy(sc); + } + + @Override + public List listPodCapacityByCapacityTypes(Long zoneId, List capacityTypes) { + SearchBuilder sb = createSearchBuilder(); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.and("capacityState", sb.entity().getCapacityState(), Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("capacityState", "Enabled"); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + sc.setParameters("capacityTypes", capacityTypes.toArray()); + return listBy(sc); + } + + @Override + public List listClusterCapacityByCapacityTypes(Long zoneId, Long podId, List capacityTypes) { + SearchBuilder sb = createSearchBuilder(); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("podId", sb.entity().getPodId(), SearchCriteria.Op.EQ); + sb.and("capacityTypes", sb.entity().getCapacityType(), SearchCriteria.Op.IN); + sb.and("capacityState", sb.entity().getCapacityState(), Op.EQ); + sb.done(); + + SearchCriteria sc = sb.create(); + sc.setParameters("capacityState", "Enabled"); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + if (podId != null) { + sc.setParameters("podId", podId); + } + sc.setParameters("capacityTypes", capacityTypes.toArray()); + return listBy(sc); + } + + @Override + public List listPodsByHostCapacities(long zoneId, int requiredCpu, long requiredRam) { TransactionLegacy txn = TransactionLegacy.currentTxn(); PreparedStatement pstmt = null; List result = new ArrayList(); diff --git a/engine/schema/src/main/java/com/cloud/configuration/ManagementServiceConfiguration.java b/engine/schema/src/main/java/com/cloud/configuration/ManagementServiceConfiguration.java index 51b7f62f56de..841447de5fd8 100644 --- a/engine/schema/src/main/java/com/cloud/configuration/ManagementServiceConfiguration.java +++ b/engine/schema/src/main/java/com/cloud/configuration/ManagementServiceConfiguration.java @@ -21,7 +21,7 @@ public interface ManagementServiceConfiguration extends Configurable { ConfigKey PingInterval = new ConfigKey("Advanced", Integer.class, "ping.interval", "60", - "Interval to send application level pings to make sure the connection is still working", false); + "Interval in seconds to send application level pings to make sure the connection is still working", false); ConfigKey PingTimeout = new ConfigKey("Advanced", Float.class, "ping.timeout", "2.5", "Multiplier to ping.interval before announcing an agent has timed out", true); public int getPingInterval(); diff --git a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDao.java b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDao.java index b978cc04bfa7..ebacbd8b3e5a 100644 --- a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDao.java +++ b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDao.java @@ -62,20 +62,6 @@ List findByOwnersAndTypeAndTag(List ownerIdList, Resource long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType); - /** - * Counts the number of CPU cores allocated for the given account. - * - * Side note: This method is not using the "resource_count" table. It is executing the actual count instead. - */ - long countCpuNumberAllocatedToAccount(long accountId); - - /** - * Counts the amount of memory allocated for the given account. - * - * Side note: This method is not using the "resource_count" table. It is executing the actual count instead. - */ - long countMemoryAllocatedToAccount(long accountId); - void removeResourceCountsForNonMatchingTags(Long ownerId, ResourceOwnerType ownerType, List types, List tags); List lockRows(Set ids); diff --git a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java index 65d7fed2d1a1..2083fb422d28 100644 --- a/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/configuration/dao/ResourceCountDaoImpl.java @@ -336,22 +336,9 @@ public long removeEntriesByOwner(long ownerId, ResourceOwnerType ownerType) { + " END)) as total " + " from vm_instance vm " + " join service_offering so on so.id = vm.service_offering_id " - + " left join user_vm_details vmd on vmd.vm_id = vm.id and vmd.name = '%s' " + + " left join vm_instance_details vmd on vmd.vm_id = vm.id and vmd.name = '%s' " + " where vm.type = 'User' and state not in ('Destroyed', 'Error', 'Expunging') and display_vm = true and account_id = ? "; - @Override - public long countCpuNumberAllocatedToAccount(long accountId) { - String sqlCountCpuNumberAllocatedToAccount = String.format(baseSqlCountComputingResourceAllocatedToAccount, ResourceType.cpu, ResourceType.cpu, "cpuNumber"); - return executeSqlCountComputingResourcesForAccount(accountId, sqlCountCpuNumberAllocatedToAccount); - } - - @Override - public long countMemoryAllocatedToAccount(long accountId) { - String serviceOfferingRamSizeField = "ram_size"; - String sqlCountCpuNumberAllocatedToAccount = String.format(baseSqlCountComputingResourceAllocatedToAccount, serviceOfferingRamSizeField, serviceOfferingRamSizeField, "memory"); - return executeSqlCountComputingResourcesForAccount(accountId, sqlCountCpuNumberAllocatedToAccount); - } - private long executeSqlCountComputingResourcesForAccount(long accountId, String sqlCountComputingResourcesAllocatedToAccount) { TransactionLegacy tx = TransactionLegacy.currentTxn(); try { diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java index f7d5c23c32cc..a8888f98ad29 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterDetailsDaoImpl.java @@ -22,11 +22,16 @@ import java.util.Map; import java.util.stream.Collectors; +import javax.inject.Inject; + import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; import org.apache.cloudstack.framework.config.ScopedConfigStorage; import org.apache.commons.collections.CollectionUtils; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.org.Cluster; +import com.cloud.utils.Pair; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -35,6 +40,9 @@ public class ClusterDetailsDaoImpl extends ResourceDetailsDaoBase implements ClusterDetailsDao, ScopedConfigStorage { + @Inject + ClusterDao clusterDao; + protected final SearchBuilder ClusterSearch; protected final SearchBuilder DetailSearch; @@ -186,4 +194,13 @@ private String getCpuMemoryOvercommitRatio(String name) { return name; } + + @Override + public Pair getParentScope(long id) { + Cluster cluster = clusterDao.findById(id); + if (cluster == null) { + return null; + } + return new Pair<>(getScope().getParent(), cluster.getDataCenterId()); + } } diff --git a/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java b/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java index 434901ef5b3b..a18097db6d6c 100644 --- a/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/ClusterVO.java @@ -85,6 +85,10 @@ public class ClusterVO implements Cluster { @Column(name = "uuid") String uuid; + @Column(name = "storage_access_groups") + String storageAccessGroups; + + public ClusterVO() { clusterType = Cluster.ClusterType.CloudManaged; allocationState = Grouping.AllocationState.Enabled; @@ -215,6 +219,14 @@ public void setArch(String arch) { this.arch = arch; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + @Override public String toString() { return String.format("Cluster {id: \"%s\", name: \"%s\", uuid: \"%s\"}", id, name, uuid); diff --git a/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java b/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java index 827b72b58b05..9b24e51a1a8b 100644 --- a/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/DataCenterVO.java @@ -142,6 +142,9 @@ public class DataCenterVO implements DataCenter { @Enumerated(value = EnumType.STRING) private DataCenter.Type type; + @Column(name = "storage_access_groups") + String storageAccessGroups; + @Override public String getDnsProvider() { return dnsProvider; @@ -485,6 +488,14 @@ public void setType(Type type) { this.type = type; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + @Override public String toString() { return String.format("Zone {\"id\": \"%s\", \"name\": \"%s\", \"uuid\": \"%s\"}", id, name, uuid); diff --git a/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java b/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java index fdda38fbc393..99ebcf2346c5 100644 --- a/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java +++ b/engine/schema/src/main/java/com/cloud/dc/HostPodVO.java @@ -71,6 +71,9 @@ public class HostPodVO implements Pod { @Column(name = "uuid") private String uuid; + @Column(name = "storage_access_groups") + String storageAccessGroups; + public HostPodVO(String name, long dcId, String gateway, String cidrAddress, int cidrSize, String description) { this.name = name; this.dataCenterId = dcId; @@ -199,6 +202,14 @@ public void setUuid(String uuid) { this.uuid = uuid; } + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + @Override public String toString() { return String.format("HostPod %s", diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java index 69b5f0e146e8..3d04f6679def 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDao.java @@ -23,6 +23,7 @@ import com.cloud.dc.ClusterVO; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; public interface ClusterDao extends GenericDao { @@ -34,7 +35,7 @@ public interface ClusterDao extends GenericDao { List getAvailableHypervisorInZone(Long zoneId); - List> listDistinctHypervisorsArchAcrossClusters(Long zoneId); + List> listDistinctHypervisorsAndArchExcludingExternalType(Long zoneId); List listByDcHyType(long dcId, String hyType); @@ -57,4 +58,10 @@ public interface ClusterDao extends GenericDao { List getClustersArchsByZone(long zoneId); List listClustersByArchAndZoneId(long zoneId, CPU.CPUArch arch); + + List listDistinctStorageAccessGroups(String name, String keyword); + + List listEnabledClusterIdsByZoneHypervisorArch(Long zoneId, HypervisorType hypervisorType, CPU.CPUArch arch); + + List listByZonesAndHypervisorType(List zoneIds, HypervisorType hypervisorType, Filter filter); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java index 59614b547456..9b6f0f7ac3a6 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterDaoImpl.java @@ -20,6 +20,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -27,6 +28,7 @@ import javax.inject.Inject; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.cpu.CPU; @@ -38,6 +40,7 @@ import com.cloud.org.Grouping; import com.cloud.org.Managed; import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.JoinBuilder; @@ -168,16 +171,26 @@ public List getAvailableHypervisorInZone(Long zoneId) { .collect(Collectors.toList()); } + /** + * Returns distinct (HypervisorType, CPUArch) pairs from clusters in the given zone, + * excluding clusters with {@link HypervisorType#External}. + * + * @param zoneId the zone ID to filter by, or {@code null} to include all zones + * @return list of unique hypervisor type and CPU architecture pairs + */ @Override - public List> listDistinctHypervisorsArchAcrossClusters(Long zoneId) { + public List> listDistinctHypervisorsAndArchExcludingExternalType(Long zoneId) { SearchBuilder sb = createSearchBuilder(); sb.select(null, Func.DISTINCT_PAIR, sb.entity().getHypervisorType(), sb.entity().getArch()); sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.NEQ); sb.done(); SearchCriteria sc = sb.create(); if (zoneId != null) { sc.setParameters("zoneId", zoneId); } + sc.setParameters("hypervisorType", HypervisorType.External); + final List clusters = search(sc, null); return clusters.stream() .map(c -> new Pair<>(c.getHypervisorType(), c.getArch())) @@ -346,4 +359,76 @@ public List listClustersByArchAndZoneId(long zoneId, CPU.CPUArch arch sc.setParameters("arch", arch); return listBy(sc); } + + @Override + public List listDistinctStorageAccessGroups(String name, String keyword) { + GenericSearchBuilder searchBuilder = createSearchBuilder(String.class); + + searchBuilder.select(null, SearchCriteria.Func.DISTINCT, searchBuilder.entity().getStorageAccessGroups()); + if (name != null) { + searchBuilder.and().op("storageAccessGroupExact", searchBuilder.entity().getStorageAccessGroups(), Op.EQ); + searchBuilder.or("storageAccessGroupPrefix", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.or("storageAccessGroupSuffix", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.or("storageAccessGroupMiddle", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.cp(); + } + if (keyword != null) { + searchBuilder.and("keyword", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + } + searchBuilder.done(); + + SearchCriteria sc = searchBuilder.create(); + if (name != null) { + sc.setParameters("storageAccessGroupExact", name); + sc.setParameters("storageAccessGroupPrefix", name + ",%"); + sc.setParameters("storageAccessGroupSuffix", "%," + name); + sc.setParameters("storageAccessGroupMiddle", "%," + name + ",%"); + } + + if (keyword != null) { + sc.setParameters("keyword", "%" + keyword + "%"); + } + + return customSearch(sc, null); + } + + @Override + public List listEnabledClusterIdsByZoneHypervisorArch(Long zoneId, HypervisorType hypervisorType, CPU.CPUArch arch) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.selectFields(sb.entity().getId()); + sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); + sb.and("allocationState", sb.entity().getAllocationState(), Op.EQ); + sb.and("managedState", sb.entity().getManagedState(), Op.EQ); + sb.and("hypervisor", sb.entity().getHypervisorType(), Op.EQ); + sb.and("arch", sb.entity().getArch(), Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("allocationState", Grouping.AllocationState.Enabled); + sc.setParameters("managedState", Managed.ManagedState.Managed); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + if (hypervisorType != null) { + sc.setParameters("hypervisor", hypervisorType); + } + if (arch != null) { + sc.setParameters("arch", arch); + } + return customSearch(sc, null); + } + + @Override + public List listByZonesAndHypervisorType(List zoneIds, HypervisorType hypervisorType, Filter filter) { + if (CollectionUtils.isEmpty(zoneIds)) { + return Collections.emptyList(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.IN); + sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("dataCenterId", zoneIds.toArray()); + sc.setParameters("hypervisorType", hypervisorType.toString()); + return listBy(sc, filter); + } } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterVSMMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterVSMMapDaoImpl.java index 02a7ac6977c2..76058d213338 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/ClusterVSMMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/ClusterVSMMapDaoImpl.java @@ -36,7 +36,6 @@ public class ClusterVSMMapDaoImpl extends GenericDaoBase final SearchBuilder VsmSearch; public ClusterVSMMapDaoImpl() { - //super(); ClusterSearch = createSearchBuilder(); ClusterSearch.and("clusterId", ClusterSearch.entity().getClusterId(), SearchCriteria.Op.EQ); @@ -82,8 +81,6 @@ public boolean remove(Long id) { TransactionLegacy txn = TransactionLegacy.currentTxn(); txn.start(); ClusterVSMMapVO cluster = createForUpdate(); - //cluster.setClusterId(null); - //cluster.setVsmId(null); update(id, cluster); diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java index dddbce31772d..0ba88f39b233 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDao.java @@ -117,4 +117,6 @@ public Integer getVlan() { List listAllZones(); List listByIds(List ids); + + List listDistinctStorageAccessGroups(String name, String keyword); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java index 7719e5adfc7e..d8ab12e82e61 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDaoImpl.java @@ -25,6 +25,7 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.utils.db.GenericSearchBuilder; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; @@ -441,4 +442,36 @@ public List listByIds(List ids) { sc.setParameters("ids", ids.toArray()); return listBy(sc); } + + @Override + public List listDistinctStorageAccessGroups(String name, String keyword) { + GenericSearchBuilder searchBuilder = createSearchBuilder(String.class); + + searchBuilder.select(null, SearchCriteria.Func.DISTINCT, searchBuilder.entity().getStorageAccessGroups()); + if (name != null) { + searchBuilder.and().op("storageAccessGroupExact", searchBuilder.entity().getStorageAccessGroups(), SearchCriteria.Op.EQ); + searchBuilder.or("storageAccessGroupPrefix", searchBuilder.entity().getStorageAccessGroups(), SearchCriteria.Op.LIKE); + searchBuilder.or("storageAccessGroupSuffix", searchBuilder.entity().getStorageAccessGroups(), SearchCriteria.Op.LIKE); + searchBuilder.or("storageAccessGroupMiddle", searchBuilder.entity().getStorageAccessGroups(), SearchCriteria.Op.LIKE); + searchBuilder.cp(); + } + if (keyword != null) { + searchBuilder.and("keyword", searchBuilder.entity().getStorageAccessGroups(), SearchCriteria.Op.LIKE); + } + searchBuilder.done(); + + SearchCriteria sc = searchBuilder.create(); + if (name != null) { + sc.setParameters("storageAccessGroupExact", name); + sc.setParameters("storageAccessGroupPrefix", name + ",%"); + sc.setParameters("storageAccessGroupSuffix", "%," + name); + sc.setParameters("storageAccessGroupMiddle", "%," + name + ",%"); + } + + if (keyword != null) { + sc.setParameters("keyword", "%" + keyword + "%"); + } + + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java index 3772f21ca361..687071699920 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/DataCenterDetailsDaoImpl.java @@ -31,7 +31,8 @@ public class DataCenterDetailsDaoImpl extends ResourceDetailsDaoBase DetailSearch; - DataCenterDetailsDaoImpl() { + public DataCenterDetailsDaoImpl() { + super(); DetailSearch = createSearchBuilder(); DetailSearch.and("zoneId", DetailSearch.entity().getResourceId(), SearchCriteria.Op.EQ); DetailSearch.and("name", DetailSearch.entity().getName(), SearchCriteria.Op.EQ); diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDao.java index b2e9b898606c..2549a0555e8c 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDao.java @@ -34,4 +34,6 @@ public interface HostPodDao extends GenericDao { public List listAllPods(Long zoneId); public List listAllPodsByCidr(long zoneId, String cidr); + + List listDistinctStorageAccessGroups(String name, String keyword); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDaoImpl.java index f1835067380f..08901c9a61ea 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/HostPodDaoImpl.java @@ -143,4 +143,36 @@ public List listAllPodsByCidr(long zoneId, String cidr) { return listBy(sc); } + @Override + public List listDistinctStorageAccessGroups(String name, String keyword) { + GenericSearchBuilder searchBuilder = createSearchBuilder(String.class); + + searchBuilder.select(null, SearchCriteria.Func.DISTINCT, searchBuilder.entity().getStorageAccessGroups()); + if (name != null) { + searchBuilder.and().op("storageAccessGroupExact", searchBuilder.entity().getStorageAccessGroups(), Op.EQ); + searchBuilder.or("storageAccessGroupPrefix", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.or("storageAccessGroupSuffix", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.or("storageAccessGroupMiddle", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.cp(); + } + if (keyword != null) { + searchBuilder.and("keyword", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + } + searchBuilder.done(); + + SearchCriteria sc = searchBuilder.create(); + if (name != null) { + sc.setParameters("storageAccessGroupExact", name); + sc.setParameters("storageAccessGroupPrefix", name + ",%"); + sc.setParameters("storageAccessGroupSuffix", "%," + name); + sc.setParameters("storageAccessGroupMiddle", "%," + name + ",%"); + } + + if (keyword != null) { + sc.setParameters("keyword", "%" + keyword + "%"); + } + + return customSearch(sc, null); + } + } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java index 84f38f054412..beab7ce2cd56 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDao.java @@ -51,6 +51,8 @@ public interface VlanDao extends GenericDao { List listVlansByNetworkId(long networkId); + List listVlansByNetworkIds(List networkIds); + List listVlansByNetworkIdIncludingRemoved(long networkId); List listVlansByPhysicalNetworkId(long physicalNetworkId); @@ -64,4 +66,6 @@ public interface VlanDao extends GenericDao { List listIpv6RangeByZoneIdAndVlanId(long zoneId, String vlanId); List listIpv6SupportingVlansByZone(long zoneId); + + List listVlansForExternalNetworkProvider(long zoneId, String detailKey); } diff --git a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java index 461a9a13b10f..5687e8588772 100644 --- a/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/dc/dao/VlanDaoImpl.java @@ -20,12 +20,15 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.dc.VlanDetailsVO; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -67,6 +70,8 @@ public class VlanDaoImpl extends GenericDaoBase implements VlanDao protected SearchBuilder ZoneVlanIp6Search; protected SearchBuilder ZoneIp6Search; protected SearchBuilder ZoneVlansSearch; + protected SearchBuilder ProviderVlanSearch; + protected SearchBuilder VlanDetailsProviderSearch; protected SearchBuilder AccountVlanMapSearch; protected SearchBuilder DomainVlanMapSearch; @@ -79,6 +84,8 @@ public class VlanDaoImpl extends GenericDaoBase implements VlanDao protected DomainVlanMapDao _domainVlanMapDao; @Inject protected IPAddressDao _ipAddressDao; + @Inject + protected VlanDetailsDao vlanDetailsDao; @Override public VlanVO findByZoneAndVlanId(long zoneId, String vlanId) { @@ -129,7 +136,7 @@ public VlanDaoImpl() { ZoneTypeSearch.done(); NetworkVlanSearch = createSearchBuilder(); - NetworkVlanSearch.and("networkId", NetworkVlanSearch.entity().getNetworkId(), SearchCriteria.Op.EQ); + NetworkVlanSearch.and("networkId", NetworkVlanSearch.entity().getNetworkId(), SearchCriteria.Op.IN); NetworkVlanSearch.done(); PhysicalNetworkVlanSearch = createSearchBuilder(); @@ -277,6 +284,19 @@ public boolean configure(String name, Map params) throws Configu ZoneVlansSearch.and("zoneId", ZoneVlansSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); ZoneVlansSearch.and("vlan", ZoneVlansSearch.entity().getVlanTag(), SearchCriteria.Op.IN); ZoneVlansSearch.done(); + + ProviderVlanSearch = createSearchBuilder(); + ProviderVlanSearch.and("removed", ProviderVlanSearch.entity().getRemoved(), SearchCriteria.Op.NULL); + ProviderVlanSearch.and("dataCenterId", ProviderVlanSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + VlanDetailsProviderSearch = vlanDetailsDao.createSearchBuilder(); + VlanDetailsProviderSearch.and("name", VlanDetailsProviderSearch.entity().getName(), SearchCriteria.Op.EQ); + VlanDetailsProviderSearch.and("value", VlanDetailsProviderSearch.entity().getValue(), SearchCriteria.Op.EQ); + ProviderVlanSearch.join("VlanDetailsProviderSearch", VlanDetailsProviderSearch, ProviderVlanSearch.entity().getId(), + VlanDetailsProviderSearch.entity().getResourceId(), JoinBuilder.JoinType.INNER); + + VlanDetailsProviderSearch.done(); + ProviderVlanSearch.done(); + return result; } @@ -374,6 +394,16 @@ public List listVlansByNetworkId(long networkId) { return listBy(sc); } + @Override + public List listVlansByNetworkIds(List networkIds) { + if (CollectionUtils.isEmpty(networkIds)) { + return Collections.emptyList(); + } + SearchCriteria sc = NetworkVlanSearch.create(); + sc.setParameters("networkId", networkIds.toArray()); + return listBy(sc); + } + @Override public List listVlansByNetworkIdIncludingRemoved(long networkId) { SearchCriteria sc = NetworkVlanSearch.create(); sc.setParameters("networkId", networkId); @@ -434,4 +464,13 @@ public List listIpv6SupportingVlansByZone(long zoneId) { return listBy(sc); } + @Override + public List listVlansForExternalNetworkProvider(long zoneId, String detailKey) { + SearchCriteria sc = ProviderVlanSearch.create(); + sc.setParameters("dataCenterId", zoneId); + sc.setJoinParameters("VlanDetailsProviderSearch", "name", detailKey); + sc.setJoinParameters("VlanDetailsProviderSearch", "value", "true"); + return search(sc, null); + } + } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java index 937d99f50520..3aee371da252 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDao.java @@ -42,5 +42,7 @@ public interface DomainDao extends GenericDao { List getDomainChildrenIds(String path); + List getDomainAndChildrenIds(long domainId); + boolean domainIdListContainsAccessibleDomain(String domainIdList, Account caller, Long domainId); } diff --git a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java index 74f2932ca433..1afa0d22dcc1 100644 --- a/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/domain/dao/DomainDaoImpl.java @@ -19,6 +19,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; @@ -238,6 +239,15 @@ public List getDomainChildrenIds(String path) { return customSearch(sc, null); } + @Override + public List getDomainAndChildrenIds(long domainId) { + DomainVO domain = findById(domainId); + if (domain != null) { + return getDomainChildrenIds(domain.getPath()); + } + return new ArrayList<>(); + } + @Override public boolean isChildDomain(Long parentId, Long childId) { if ((parentId == null) || (childId == null)) { @@ -252,7 +262,7 @@ public boolean isChildDomain(Long parentId, Long childId) { SearchCriteria sc = DomainPairSearch.create(); sc.setParameters("id", parentId, childId); - List domainPair = listBy(sc); + List domainPair = listIncludingRemovedBy(sc); if ((domainPair != null) && (domainPair.size() == 2)) { DomainVO d1 = domainPair.get(0); diff --git a/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java b/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java index 3fc9fda94873..41ecec0c7fb8 100644 --- a/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java +++ b/engine/schema/src/main/java/com/cloud/event/UsageEventVO.java @@ -75,6 +75,9 @@ public enum DynamicParameters { @Column(name = "virtual_size") private Long virtualSize; + @Column(name = "vm_id") + private Long vmId; + public UsageEventVO() { } @@ -143,6 +146,18 @@ public UsageEventVO(String usageType, long accountId, long zoneId, long vmId, lo this.offeringId = securityGroupId; } + public UsageEventVO(String usageType, long accountId, long zoneId, long resourceId, Long offeringId, Long templateId, Long size, Long vmId, String resourceName) { + this.type = usageType; + this.accountId = accountId; + this.zoneId = zoneId; + this.resourceId = resourceId; + this.offeringId = offeringId; + this.templateId = templateId; + this.size = size; + this.vmId = vmId; + this.resourceName = resourceName; + } + @Override public long getId() { return id; @@ -248,4 +263,11 @@ public void setVirtualSize(Long virtualSize) { this.virtualSize = virtualSize; } + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } } diff --git a/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java b/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java index e748e98900eb..9417ddd12595 100644 --- a/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/event/dao/EventDaoImpl.java @@ -18,8 +18,10 @@ import java.util.Date; import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.event.Event.State; @@ -29,12 +31,13 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; -import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.db.UpdateBuilder; @Component public class EventDaoImpl extends GenericDaoBase implements EventDao { protected final SearchBuilder CompletedEventSearch; protected final SearchBuilder ToArchiveOrDeleteEventSearch; + protected final SearchBuilder ArchiveByIdsSearch; public EventDaoImpl() { CompletedEventSearch = createSearchBuilder(); @@ -51,6 +54,10 @@ public EventDaoImpl() { ToArchiveOrDeleteEventSearch.and("createdDateL", ToArchiveOrDeleteEventSearch.entity().getCreateDate(), Op.LTEQ); ToArchiveOrDeleteEventSearch.and("archived", ToArchiveOrDeleteEventSearch.entity().getArchived(), Op.EQ); ToArchiveOrDeleteEventSearch.done(); + + ArchiveByIdsSearch = createSearchBuilder(); + ArchiveByIdsSearch.and("id", ArchiveByIdsSearch.entity().getId(), Op.IN); + ArchiveByIdsSearch.done(); } @Override @@ -100,16 +107,16 @@ public List listToArchiveOrDeleteEvents(List ids, String type, Da @Override public void archiveEvents(List events) { - if (events != null && !events.isEmpty()) { - TransactionLegacy txn = TransactionLegacy.currentTxn(); - txn.start(); - for (EventVO event : events) { - event = lockRow(event.getId(), true); - event.setArchived(true); - update(event.getId(), event); - txn.commit(); - } - txn.close(); + if (CollectionUtils.isEmpty(events)) { + return; } + + List ids = events.stream().map(EventVO::getId).collect(Collectors.toList()); + SearchCriteria sc = ArchiveByIdsSearch.create(); + sc.setParameters("id", ids.toArray(new Object[ids.size()])); + EventVO eventForUpdate = createForUpdate(); + eventForUpdate.setArchived(true); + UpdateBuilder ub = getUpdateBuilder(eventForUpdate); + update(ub, sc, null); } } diff --git a/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java b/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java index fdef509da5bd..bce9c474e2d2 100644 --- a/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/event/dao/UsageEventDaoImpl.java @@ -45,11 +45,11 @@ public class UsageEventDaoImpl extends GenericDaoBase implem private final SearchBuilder latestEventsSearch; private final SearchBuilder IpeventsSearch; private static final String COPY_EVENTS = - "INSERT INTO cloud_usage.usage_event (id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size) " - + "SELECT id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size FROM cloud.usage_event vmevt WHERE vmevt.id > ? and vmevt.id <= ? "; + "INSERT INTO cloud_usage.usage_event (id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size, vm_id) " + + "SELECT id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size, vm_id FROM cloud.usage_event vmevt WHERE vmevt.id > ? and vmevt.id <= ? "; private static final String COPY_ALL_EVENTS = - "INSERT INTO cloud_usage.usage_event (id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size) " - + "SELECT id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size FROM cloud.usage_event vmevt WHERE vmevt.id <= ?"; + "INSERT INTO cloud_usage.usage_event (id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size, vm_id) " + + "SELECT id, type, account_id, created, zone_id, resource_id, resource_name, offering_id, template_id, size, resource_type, virtual_size, vm_id FROM cloud.usage_event vmevt WHERE vmevt.id <= ?"; private static final String COPY_EVENT_DETAILS = "INSERT INTO cloud_usage.usage_event_details (id, usage_event_id, name, value) " + "SELECT id, usage_event_id, name, value FROM cloud.usage_event_details vmevtDetails WHERE vmevtDetails.usage_event_id > ? and vmevtDetails.usage_event_id <= ? "; private static final String COPY_ALL_EVENT_DETAILS = "INSERT INTO cloud_usage.usage_event_details (id, usage_event_id, name, value) " diff --git a/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java b/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java new file mode 100644 index 000000000000..2410077c84ad --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/GpuCardVO.java @@ -0,0 +1,147 @@ +// 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.gpu; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gpu.GpuCard; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "gpu_card") +public class GpuCardVO implements GpuCard { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "device_id") + private String deviceId; + + @Column(name = "device_name") + private String deviceName; + + @Column(name = "name") + private String name; + + @Column(name = "vendor_name") + private String vendorName; + + @Column(name = "vendor_id") + private String vendorId; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + public GpuCardVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public GpuCardVO(String deviceId, String deviceName, String name, String vendorName, String vendorId) { + this.uuid = UUID.randomUUID().toString(); + this.deviceId = deviceId; + this.deviceName = deviceName; + this.name = name; + this.vendorName = vendorName; + this.vendorId = vendorId; + this.created = new Date(); + } + + @Override + public String toString() { + return String.format("GPUCard %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "name", "deviceId", "deviceName", "vendorId", "vendorName")); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + @Override + public String getDeviceName() { + return deviceName; + } + + public void setDeviceName(String deviceName) { + this.deviceName = deviceName; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getVendorName() { + return vendorName; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + @Override + public String getVendorId() { + return vendorId; + } + + public void setVendorId(String vendorId) { + this.vendorId = vendorId; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public String getGroupName() { + return "Group of " + getVendorName() + " " + getDeviceName() + " GPUs"; + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java b/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java new file mode 100644 index 000000000000..ac20e74c3604 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/GpuDeviceVO.java @@ -0,0 +1,200 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.gpu; + +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.UUID; + +@Entity +@Table(name = "gpu_device") +public class GpuDeviceVO implements GpuDevice { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "card_id") + private long cardId; + + @Column(name = "vgpu_profile_id") + private long vgpuProfileId; + + @Column(name = "bus_address") + private String busAddress; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "vm_id") + private Long vmId; + + @Column(name = "type") + @Enumerated(value = EnumType.STRING) + private DeviceType type = DeviceType.PCI; + + @Column(name = "state") + @Enumerated(value = EnumType.STRING) + private State state = State.Free; + + @Column(name = "managed_state") + @Enumerated(value = EnumType.STRING) + private ManagedState managedState = ManagedState.Managed; + + @Column(name = "parent_gpu_device_id") + private Long parentGpuDeviceId; + + @Column(name = "numa_node") + private String numaNode; + + @Column(name = "pci_root") + private String pciRoot; + + public GpuDeviceVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public GpuDeviceVO(long cardId, long vgpuProfileId, String busAddress, long hostId, Long parentGpuDeviceId, + String numaNode, String pciRoot) { + this.uuid = UUID.randomUUID().toString(); + this.cardId = cardId; + this.vgpuProfileId = vgpuProfileId; + this.busAddress = busAddress; + this.hostId = hostId; + this.parentGpuDeviceId = parentGpuDeviceId; + this.numaNode = numaNode; + this.pciRoot = pciRoot; + } + + @Override + public String toString() { + return String.format("GpuDevice %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "cardId", "vgpuProfileId", "busAddress", "hostId", "vmId", + "parentGpuDeviceId", "numaNode", "pciRoot", "state", "resourceState")); + } + + @Override + public long getId() { + return id; + } + + public String getUuid() { + return uuid; + } + + public long getCardId() { + return cardId; + } + + public void setCardId(long cardId) { + this.cardId = cardId; + } + + public long getVgpuProfileId() { + return vgpuProfileId; + } + + public void setVgpuProfileId(long vgpuProfileId) { + this.vgpuProfileId = vgpuProfileId; + } + + public String getBusAddress() { + return busAddress; + } + + public void setBusAddress(String busAddress) { + this.busAddress = busAddress; + } + + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + @Override + public State getState() { + return state; + } + + public void setState(State state) { + this.state = state; + } + + public DeviceType getType() { + return type; + } + + public void setType(DeviceType type) { + this.type = type; + } + + public ManagedState getManagedState() { + return managedState; + } + + public void setManagedState(ManagedState managedState) { + this.managedState = managedState; + } + + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public Long getParentGpuDeviceId() { + return parentGpuDeviceId; + } + + public void setParentGpuDeviceId(Long parentGpuDeviceId) { + this.parentGpuDeviceId = parentGpuDeviceId; + } + + public String getNumaNode() { + return numaNode; + } + + public void setNumaNode(String numaNode) { + this.numaNode = numaNode; + } + + public String getPciRoot() { + return pciRoot; + } + + public void setPciRoot(String pciRoot) { + this.pciRoot = pciRoot; + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java b/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java index 5bbf90854ee6..4944d51f1b44 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java +++ b/engine/schema/src/main/java/com/cloud/gpu/VGPUTypesVO.java @@ -40,19 +40,19 @@ public class VGPUTypesVO implements InternalIdentity { private String vgpuType; @Column(name="video_ram") - private long videoRam; + private Long videoRam; @Column(name="max_heads") - private long maxHeads; + private Long maxHeads; @Column(name="max_resolution_x") - private long maxResolutionX; + private Long maxResolutionX; @Column(name="max_resolution_y") - private long maxResolutionY; + private Long maxResolutionY; @Column(name="max_vgpu_per_pgpu") - private long maxVgpuPerPgpu; + private Long maxVgpuPerPgpu; @Column(name="remaining_capacity") private long remainingCapacity; @@ -63,7 +63,7 @@ public class VGPUTypesVO implements InternalIdentity { protected VGPUTypesVO() { } - public VGPUTypesVO(long gpuGroupId, String vgpuType, long videoRam, long maxHeads, long maxResolutionX, long maxResolutionY, long maxVgpuPerPgpu, + public VGPUTypesVO(long gpuGroupId, String vgpuType, Long videoRam, Long maxHeads, Long maxResolutionX, Long maxResolutionY, Long maxVgpuPerPgpu, long remainingCapacity, long maxCapacity) { this.gpuGroupId = gpuGroupId; this.vgpuType = vgpuType; @@ -92,43 +92,43 @@ public void setVgpuType(String vgpuType) { this.vgpuType = vgpuType; } - public long getVideoRam() { + public Long getVideoRam() { return videoRam; } - public void setVideoRam(long videoRam) { + public void setVideoRam(Long videoRam) { this.videoRam = videoRam; } - public long getMaxHeads() { + public Long getMaxHeads() { return maxHeads; } - public void setMaxHeads(long maxHeads) { + public void setMaxHeads(Long maxHeads) { this.maxHeads = maxHeads; } - public long getMaxResolutionX() { + public Long getMaxResolutionX() { return maxResolutionX; } - public void setMaxResolutionX(long maxResolutionX) { + public void setMaxResolutionX(Long maxResolutionX) { this.maxResolutionX = maxResolutionX; } - public long getMaxResolutionY() { + public Long getMaxResolutionY() { return maxResolutionY; } - public void setMaxResolutionY(long maxResolutionY) { + public void setMaxResolutionY(Long maxResolutionY) { this.maxResolutionY = maxResolutionY; } - public long getMaxVgpuPerPgpu() { + public Long getMaxVgpuPerPgpu() { return maxVgpuPerPgpu; } - public void setMaxVgpuPerPgpu(long maxVgpuPerPgpu) { + public void setMaxVgpuPerPgpu(Long maxVgpuPerPgpu) { this.maxVgpuPerPgpu = maxVgpuPerPgpu; } diff --git a/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java b/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java new file mode 100644 index 000000000000..86f5eb94415b --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/VgpuProfileVO.java @@ -0,0 +1,191 @@ +// 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.gpu; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gpu.VgpuProfile; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "vgpu_profile") +public class VgpuProfileVO implements VgpuProfile { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name; + + @Column(name = "description") + private String description; + + @Column(name = "card_id") + private Long cardId; + + @Column(name = "max_vgpu_per_pgpu") + private Long maxVgpuPerPgpu; + + @Column(name = "video_ram") + private Long videoRam; + + @Column(name = "max_heads") + private Long maxHeads; + + @Column(name = "max_resolution_x") + private Long maxResolutionX; + + @Column(name = "max_resolution_y") + private Long maxResolutionY; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + public VgpuProfileVO() { + this.uuid = UUID.randomUUID().toString(); + } + + public VgpuProfileVO(String name, String description, Long gpuCardId, Long maxVgpuPerPgpu) { + this.uuid = UUID.randomUUID().toString(); + this.name = name; + this.description = description; + this.cardId = gpuCardId; + this.maxVgpuPerPgpu = maxVgpuPerPgpu; + this.created = new Date(); + } + + + public VgpuProfileVO(String name, String description, Long gpuCardId, Long maxVgpuPerPgpu, Long videoRam, Long maxHeads, Long maxResolutionX, Long maxResolutionY) { + this.uuid = UUID.randomUUID().toString(); + this.name = name; + this.description = description; + this.cardId = gpuCardId; + this.maxVgpuPerPgpu = maxVgpuPerPgpu; + this.videoRam = videoRam; + this.maxHeads = maxHeads; + this.maxResolutionX = maxResolutionX; + this.maxResolutionY = maxResolutionY; + this.created = new Date(); + } + + @Override + public String toString() { + return String.format("VgpuProfile %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "name", "cardId")); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Long getCardId() { + return cardId; + } + + public void setCardId(Long cardId) { + this.cardId = cardId; + } + + @Override + public Long getMaxVgpuPerPgpu() { + return maxVgpuPerPgpu; + } + + public void setMaxVgpuPerPgpu(Long maxVgpuPerPgpu) { + this.maxVgpuPerPgpu = maxVgpuPerPgpu; + } + + @Override + public Long getVideoRam() { + return videoRam; + } + + public void setVideoRam(Long videoRam) { + this.videoRam = videoRam; + } + + @Override + public Long getMaxHeads() { + return maxHeads; + } + + public void setMaxHeads(Long maxHeads) { + this.maxHeads = maxHeads; + } + + @Override + public Long getMaxResolutionX() { + return maxResolutionX; + } + + public void setMaxResolutionX(Long maxResolutionX) { + this.maxResolutionX = maxResolutionX; + } + + @Override + public Long getMaxResolutionY() { + return maxResolutionY; + } + + public void setMaxResolutionY(Long maxResolutionY) { + this.maxResolutionY = maxResolutionY; + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java new file mode 100644 index 000000000000..4463a690ae92 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDao.java @@ -0,0 +1,39 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface GpuCardDao extends GenericDao { + + /** + * Find GPU card by vendor and device id + * + * @param vendorId the vendor id + * @param deviceId the device id + * @return GpuCardVO + */ + GpuCardVO findByVendorIdAndDeviceId(String vendorId, String deviceId); + + Pair, Integer> searchAndCountGpuCards( + Long id, String keyword, String vendorId, String vendorName, + String deviceId, String deviceName, boolean activeOnly, Long startIndex, Long pageSize); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java new file mode 100644 index 000000000000..8aad85d45086 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuCardDaoImpl.java @@ -0,0 +1,122 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.List; + +@Component +public class GpuCardDaoImpl extends GenericDaoBase implements GpuCardDao { + + private final SearchBuilder allFieldSearch; + + @Inject + private GpuDeviceDao gpuDeviceDao; + + public GpuCardDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ); + allFieldSearch.and("vendorId", allFieldSearch.entity().getVendorId(), SearchCriteria.Op.EQ); + allFieldSearch.and("vendorName", allFieldSearch.entity().getVendorName(), SearchCriteria.Op.EQ); + allFieldSearch.and("deviceId", allFieldSearch.entity().getDeviceId(), SearchCriteria.Op.EQ); + allFieldSearch.and("deviceName", allFieldSearch.entity().getDeviceName(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + } + + @Override + public GpuCardVO findByVendorIdAndDeviceId(String vendorId, String deviceId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("vendorId", vendorId); + sc.setParameters("deviceId", deviceId); + return findOneBy(sc); + } + + @Override + public Pair, Integer> searchAndCountGpuCards(Long id, String keyword, String vendorId, + String vendorName, String deviceId, String deviceName, boolean activeOnly, Long startIndex, Long pageSize + ) { + + Filter searchFilter = new Filter(GpuCardVO.class, "id", true, startIndex, pageSize); + SearchBuilder sb = createSearchBuilder(); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + if (keyword != null) { + sb.op("nameKeyword", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("deviceNameKeyword", sb.entity().getDeviceName(), SearchCriteria.Op.LIKE); + sb.and("vendorNameKeyword", sb.entity().getVendorName(), SearchCriteria.Op.LIKE); + sb.cp(); + } + if (vendorId != null) { + sb.and("vendorId", sb.entity().getVendorId(), SearchCriteria.Op.EQ); + } + if (vendorName != null) { + sb.and("vendorName", sb.entity().getVendorName(), SearchCriteria.Op.EQ); + } + if (deviceId != null) { + sb.and("deviceId", sb.entity().getDeviceId(), SearchCriteria.Op.EQ); + } + if (deviceName != null) { + sb.and("deviceName", sb.entity().getDeviceName(), SearchCriteria.Op.EQ); + } + if (activeOnly) { + sb.and("ids", sb.entity().getId(), SearchCriteria.Op.IN); + } + sb.done(); + + // Build search criteria + SearchCriteria sc = sb.create(); + if (id != null) { + sc.setParameters("id", id); + } + if (keyword != null) { + sc.setParameters("nameKeyword", "%" + keyword + "%"); + sc.setParameters("deviceNameKeyword", "%" + keyword + "%"); + sc.setParameters("vendorNameKeyword", "%" + keyword + "%"); + } + if (vendorId != null) { + sc.setParameters("vendorId", vendorId); + } + if (vendorName != null) { + sc.setParameters("vendorName", vendorName); + } + if (deviceId != null) { + sc.setParameters("deviceId", deviceId); + } + if (deviceName != null) { + sc.setParameters("deviceName", deviceName); + } + if (activeOnly) { + List cardIds = gpuDeviceDao.getDistinctGpuCardIds(); + if (cardIds.isEmpty()) { + return new Pair<>(List.of(), 0); + } + sc.setParameters("ids", cardIds.toArray()); + } + + return searchAndCount(sc, searchFilter); + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java new file mode 100644 index 000000000000..e362f23888d3 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDao.java @@ -0,0 +1,71 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuDeviceVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface GpuDeviceDao extends GenericDao { + + List listByIds(List ids); + + /** + * Find GPU device by host ID and bus address + * + * @param hostId the host ID + * @param busAddress the PCI bus address + * @return GpuDeviceVO + */ + GpuDeviceVO findByHostIdAndBusAddress(long hostId, String busAddress); + + /** + * List GPU devices by host ID + * + * @param hostId the ID of the host + * @return a list of GPU devices for the host + */ + List listByHostId(long hostId); + + /** + * List GPU devices by VM ID + * + * @param vmId the VM ID + * @return list of GpuDeviceVO + */ + List listByVmId(long vmId); + + boolean isVgpuProfileInUse(long vgpuProfileId); + + boolean isGpuCardInUse(long cardId); + + List listByHostAndVm(Long hostId, long vmId); + + List listDevicesForAllocation(Long hostId, Long vgpuProfileId); + + Pair, Integer> searchAndCountGpuDevices( + Long id, String keyword, Long hostId, Long vmId, Long gpuCardId, Long vgpuProfileId, + Long startIndex, Long pageSize); + + List getDistinctGpuCardIds(); + + List getDistinctVgpuProfileIds(); + + List listByParentGpuDeviceId(Long parentGpuDeviceId); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java new file mode 100644 index 000000000000..bd7032aff27b --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/GpuDeviceDaoImpl.java @@ -0,0 +1,260 @@ +// 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.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.gpu.GpuDeviceVO; +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.gpu.GpuDevice; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@Component +public class GpuDeviceDaoImpl extends GenericDaoBase implements GpuDeviceDao { + + private static final String IDS = "ids"; + private static final String HOST_ID = "hostId"; + private static final String VM_ID = "vmId"; + private static final String BUS_ADDRESS = "busAddress"; + private static final String CARD_ID = "cardId"; + private static final String VGPU_PROFILE_ID = "vgpuProfileId"; + private static final String PARENT_GPU_DEVICE_ID = "parentGpuDeviceId"; + private static final String STATE = "state"; + private static final String MANAGED_STATE = "managedState"; + private static final String TYPE = "type"; + private final SearchBuilder allFieldSearch; + private SearchBuilder devicesForAllocationSearch; + @Inject + private GpuCardDao gpuCardDao; + @Inject + private VgpuProfileDao vgpuProfileDao; + + public GpuDeviceDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and(IDS, allFieldSearch.entity().getId(), SearchCriteria.Op.IN); + allFieldSearch.and(HOST_ID, allFieldSearch.entity().getHostId(), SearchCriteria.Op.EQ); + allFieldSearch.and(CARD_ID, allFieldSearch.entity().getCardId(), SearchCriteria.Op.EQ); + allFieldSearch.and(BUS_ADDRESS, allFieldSearch.entity().getBusAddress(), SearchCriteria.Op.EQ); + allFieldSearch.and(STATE, allFieldSearch.entity().getState(), SearchCriteria.Op.EQ); + allFieldSearch.and(VGPU_PROFILE_ID, allFieldSearch.entity().getVgpuProfileId(), SearchCriteria.Op.EQ); + allFieldSearch.and(PARENT_GPU_DEVICE_ID, allFieldSearch.entity().getParentGpuDeviceId(), SearchCriteria.Op.EQ); + allFieldSearch.and(VM_ID, allFieldSearch.entity().getVmId(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + + devicesForAllocationSearch = createSearchBuilder(); + devicesForAllocationSearch.and(HOST_ID, devicesForAllocationSearch.entity().getHostId(), SearchCriteria.Op.EQ); + devicesForAllocationSearch.and(VGPU_PROFILE_ID, devicesForAllocationSearch.entity().getVgpuProfileId(), SearchCriteria.Op.IN); + devicesForAllocationSearch.and(STATE, devicesForAllocationSearch.entity().getState(), SearchCriteria.Op.EQ); + devicesForAllocationSearch.and(MANAGED_STATE, devicesForAllocationSearch.entity().getManagedState(), SearchCriteria.Op.EQ); + devicesForAllocationSearch.and(TYPE, devicesForAllocationSearch.entity().getType(), SearchCriteria.Op.NEQ); + devicesForAllocationSearch.done(); + } + + @Override + public boolean configure(String name, Map params) throws ConfigurationException { + return super.configure(name, params); + } + + @Override + public List listByIds(List ids) { + if (CollectionUtils.isEmpty(ids)) { + return Collections.emptyList(); + } + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(IDS, ids.toArray()); + return listBy(sc); + } + + @Override + public GpuDeviceVO findByHostIdAndBusAddress(long hostId, String busAddress) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(HOST_ID, hostId); + sc.setParameters(BUS_ADDRESS, busAddress); + return findOneBy(sc); + } + + @Override + public List listByHostId(long hostId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(HOST_ID, hostId); + return listBy(sc); + } + + @Override + public List listByVmId(long vmId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(VM_ID, vmId); + return listBy(sc); + } + + @Override + public boolean isVgpuProfileInUse(long vgpuProfileId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(VGPU_PROFILE_ID, vgpuProfileId); + return getCount(sc) > 0; + } + + @Override + public boolean isGpuCardInUse(long cardId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(CARD_ID, cardId); + return getCount(sc) > 0; + } + + @Override + public List listByHostAndVm(Long hostId, long vmId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(HOST_ID, hostId); + sc.setParameters(VM_ID, vmId); + return search(sc, null); + } + + @Override + public List listDevicesForAllocation(Long hostId, Long vgpuProfileId) { + SearchCriteria sc = devicesForAllocationSearch.create(); + sc.setParameters(HOST_ID, hostId); + sc.setParameters(VGPU_PROFILE_ID, vgpuProfileId); + sc.setParameters(STATE, GpuDevice.State.Free); + sc.setParameters(MANAGED_STATE, GpuDevice.ManagedState.Managed); + sc.setParameters(TYPE, GpuDevice.DeviceType.VGPUOnly); + return search(sc, null); + } + + @Override + public Pair, Integer> searchAndCountGpuDevices(Long id, String keyword, Long hostId, Long vmId, + Long gpuCardId, Long vgpuProfileId, Long startIndex, Long pageSize) { + Filter searchFilter = new Filter(GpuDeviceVO.class, "id", true, startIndex, pageSize); + SearchBuilder sb = createSearchBuilder(); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + if (hostId != null) { + sb.and("hostId", sb.entity().getHostId(), SearchCriteria.Op.EQ); + } + if (vmId != null) { + sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ); + } + if (gpuCardId != null) { + sb.and("cardId", sb.entity().getCardId(), SearchCriteria.Op.EQ); + } + if (vgpuProfileId != null) { + sb.and("vgpuProfileId", sb.entity().getVgpuProfileId(), SearchCriteria.Op.EQ); + } + if (keyword != null) { + SearchBuilder cardSb = gpuCardDao.createSearchBuilder(); + SearchBuilder profileSb = vgpuProfileDao.createSearchBuilder(); + sb.join("cardJoin", cardSb, sb.entity().getCardId(), cardSb.entity().getId(), JoinBuilder.JoinType.INNER); + sb.join("profileJoin", profileSb, sb.entity().getCardId(), profileSb.entity().getId(), + JoinBuilder.JoinType.INNER); + + sb.op("cardNameKeyword", cardSb.entity().getName(), SearchCriteria.Op.LIKE); + sb.or("cardNameKeyword", cardSb.entity().getVendorName(), SearchCriteria.Op.LIKE); + sb.or("cardNameKeyword", cardSb.entity().getDeviceName(), SearchCriteria.Op.LIKE); + + sb.op("profileNameKeyword", profileSb.entity().getName(), SearchCriteria.Op.LIKE); + sb.op("profileDescriptionKeyword", profileSb.entity().getDescription(), SearchCriteria.Op.LIKE); + sb.cp(); + } + + sb.done(); + + // Build search criteria + SearchCriteria sc = sb.create(); + if (id != null) { + sc.setParameters("id", id); + } + if (hostId != null) { + sc.setParameters("hostId", hostId); + } + if (vmId != null) { + sc.setParameters("vmId", vmId); + } + if (gpuCardId != null) { + sc.setParameters("cardId", gpuCardId); + } + if (vgpuProfileId != null) { + sc.setParameters("vgpuProfileId", vgpuProfileId); + } + + if (keyword != null) { + sc.setJoinParameters("cardJoin", "cardNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("cardJoin", "cardNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("cardJoin", "cardNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("profileJoin", "profileNameKeyword", "%" + keyword + "%"); + sc.setJoinParameters("profileJoin", "profileDescriptionKeyword", "%" + keyword + "%"); + } + + return searchAndCount(sc, searchFilter); + } + + @Override + public List getDistinctGpuCardIds() { + SearchBuilder sb = createSearchBuilder(); + sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getCardId()); + sb.done(); + SearchCriteria sc = sb.create(); + + List gpuDevices = listBy(sc); + if (CollectionUtils.isEmpty(gpuDevices)) { + return Collections.emptyList(); + } + + return gpuDevices.stream() + .map(GpuDeviceVO::getCardId) + .distinct() + .collect(Collectors.toList()); + } + + @Override + public List getDistinctVgpuProfileIds() { + SearchBuilder sb = createSearchBuilder(); + sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getVgpuProfileId()); + sb.done(); + SearchCriteria sc = sb.create(); + + List gpuDevices = listBy(sc); + if (CollectionUtils.isEmpty(gpuDevices)) { + return Collections.emptyList(); + } + + return gpuDevices.stream() + .map(GpuDeviceVO::getVgpuProfileId) + .distinct() + .collect(Collectors.toList()); + } + + @Override + public List listByParentGpuDeviceId(Long parentGpuDeviceId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters(PARENT_GPU_DEVICE_ID, parentGpuDeviceId); + return listBy(sc); + } +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java index 8e4f2f742ac5..99e336175393 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDao.java @@ -19,6 +19,7 @@ import java.util.List; import com.cloud.gpu.HostGpuGroupsVO; +import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; public interface HostGpuGroupsDao extends GenericDao { @@ -57,4 +58,15 @@ public interface HostGpuGroupsDao extends GenericDao { */ void persist(long hostId, List gpuGroups); + + /** + * Returns max and remaining GPU capacity + * + * @param dcId + * @param podId + * @param clusterId + * @param hostId + * @return Pair containing max GPU capacity and remaining GPU capacity + */ + Pair getGpuStats(Long dcId, Long podId, Long clusterId, Long hostId); } diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java index 30535c7e27d5..343b144597c5 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/HostGpuGroupsDaoImpl.java @@ -16,9 +16,16 @@ // under the License. package com.cloud.gpu.dao; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; +import com.cloud.utils.Pair; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; import org.springframework.stereotype.Component; import com.cloud.gpu.HostGpuGroupsVO; @@ -87,4 +94,75 @@ public void deleteGpuEntries(long hostId) { sc.setParameters("hostId", hostId); remove(sc); } + + @Override + public Pair getGpuStats(Long dcId, Long podId, Long clusterId, Long hostId) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + Pair result = null; + List resourceIdList = new ArrayList<>(); + String query = getStatsQuery(resourceIdList, dcId, podId, clusterId, hostId); + + try { + PreparedStatement pstmt = txn.prepareAutoCloseStatement(query); + for (int i = 0; i < resourceIdList.size(); i++) { + pstmt.setLong(1 + i, resourceIdList.get(i)); + } + + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + result = new Pair<>(rs.getLong(1), rs.getLong(2)); + } + return result; + } catch (SQLException e) { + throw new CloudRuntimeException("Error while fetching GPU stats: " + e.getMessage(), e); + } catch (Throwable e) { + throw new CloudRuntimeException("Caught: " + query, e); + } + } + + private String getStatsQuery(List resourceIdList, Long dcId, Long podId, Long clusterId, Long hostId) { + StringBuilder query = new StringBuilder("SELECT SUM(max_capacity), SUM(remaining_capacity)" + + "FROM vgpu_types " + + "WHERE" + + " gpu_group_id IN (" + + " SELECT" + + " host_gpu_groups.id" + + " FROM" + + " host_gpu_groups" + + " INNER JOIN host ON host.id = host_gpu_groups.host_id "); + if (dcId != null) { + query.append("WHERE host.data_center_id = ? "); + resourceIdList.add(dcId); + } + + if (podId != null) { + if (resourceIdList.isEmpty()) { + query.append("WHERE "); + } else { + query.append("AND "); + } + query.append(" host.pod_id = ? "); + resourceIdList.add(podId); + } + if (clusterId != null) { + if (resourceIdList.isEmpty()) { + query.append("WHERE "); + } else { + query.append("AND "); + } + query.append(" host.cluster_id = ? "); + resourceIdList.add(clusterId); + } + if (hostId != null) { + if (resourceIdList.isEmpty()) { + query.append("WHERE "); + } else { + query.append("AND "); + } + query.append(" host.id = ? "); + resourceIdList.add(hostId); + } + query.append(" )"); + return query.toString(); + } } diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java index edc5e1f67c86..524feed24679 100644 --- a/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VGPUTypesDaoImpl.java @@ -16,19 +16,6 @@ //under the License. package com.cloud.gpu.dao; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map.Entry; - -import javax.inject.Inject; - -import org.springframework.stereotype.Component; - import com.cloud.agent.api.VgpuTypesInfo; import com.cloud.gpu.HostGpuGroupsVO; import com.cloud.gpu.VGPUTypesVO; @@ -37,30 +24,42 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map.Entry; @Component public class VGPUTypesDaoImpl extends GenericDaoBase implements VGPUTypesDao { - private final SearchBuilder _searchByGroupId; - private final SearchBuilder _searchByGroupIdVGPUType; - - @Inject protected HostGpuGroupsDao _hostGpuGroupsDao; + @Inject + protected HostGpuGroupsDao hostGpuGroupsDao; private static final String LIST_ZONE_POD_CLUSTER_WIDE_GPU_CAPACITIES = "SELECT host_gpu_groups.group_name, vgpu_type, max_vgpu_per_pgpu, SUM(remaining_capacity) AS remaining_capacity, SUM(max_capacity) AS total_capacity FROM" + - " `cloud`.`vgpu_types` INNER JOIN `cloud`.`host_gpu_groups` ON vgpu_types.gpu_group_id = host_gpu_groups.id INNER JOIN `cloud`.`host`" + - " ON host_gpu_groups.host_id = host.id WHERE host.type = 'Routing' AND host.data_center_id = ?"; + " `cloud`.`vgpu_types` INNER JOIN `cloud`.`host_gpu_groups` ON vgpu_types.gpu_group_id = host_gpu_groups.id INNER JOIN `cloud`.`host`" + + " ON host_gpu_groups.host_id = host.id WHERE host.type = 'Routing' AND vgpu_types.max_capacity > 0 AND host.data_center_id = ?"; + + private final SearchBuilder searchByGroupId; + private final SearchBuilder searchByGroupIdVGPUType; public VGPUTypesDaoImpl() { - _searchByGroupId = createSearchBuilder(); - _searchByGroupId.and("groupId", _searchByGroupId.entity().getGpuGroupId(), SearchCriteria.Op.EQ); - _searchByGroupId.done(); + searchByGroupId = createSearchBuilder(); + searchByGroupId.and("groupId", searchByGroupId.entity().getGpuGroupId(), SearchCriteria.Op.EQ); + searchByGroupId.done(); - _searchByGroupIdVGPUType = createSearchBuilder(); - _searchByGroupIdVGPUType.and("groupId", _searchByGroupIdVGPUType.entity().getGpuGroupId(), SearchCriteria.Op.EQ); - _searchByGroupIdVGPUType.and("vgpuType", _searchByGroupIdVGPUType.entity().getVgpuType(), SearchCriteria.Op.EQ); - _searchByGroupIdVGPUType.done(); + searchByGroupIdVGPUType = createSearchBuilder(); + searchByGroupIdVGPUType.and("groupId", searchByGroupIdVGPUType.entity().getGpuGroupId(), SearchCriteria.Op.EQ); + searchByGroupIdVGPUType.and("vgpuType", searchByGroupIdVGPUType.entity().getVgpuType(), SearchCriteria.Op.EQ); + searchByGroupIdVGPUType.done(); } @Override @@ -83,7 +82,7 @@ public List listGPUCapacities(Long dcId, Long podId, Long cluster finalQuery.append(" AND host.cluster_id = ?"); resourceIdList.add(clusterId); } - finalQuery.append(" GROUP BY host_gpu_groups.group_name, vgpu_type"); + finalQuery.append(" GROUP BY host_gpu_groups.group_name, vgpu_type, max_vgpu_per_pgpu"); try { pstmt = txn.prepareAutoCloseStatement(finalQuery.toString()); @@ -106,14 +105,14 @@ public List listGPUCapacities(Long dcId, Long podId, Long cluster @Override public List listByGroupId(long groupId) { - SearchCriteria sc = _searchByGroupId.create(); + SearchCriteria sc = searchByGroupId.create(); sc.setParameters("groupId", groupId); return listBy(sc); } @Override public VGPUTypesVO findByGroupIdVGPUType(long groupId, String vgpuType) { - SearchCriteria sc = _searchByGroupIdVGPUType.create(); + SearchCriteria sc = searchByGroupIdVGPUType.create(); sc.setParameters("groupId", groupId); sc.setParameters("vgpuType", vgpuType); return findOneBy(sc); @@ -124,7 +123,7 @@ public void persist(long hostId, HashMap> Iterator>> it1 = groupDetails.entrySet().iterator(); while (it1.hasNext()) { Entry> entry = it1.next(); - HostGpuGroupsVO gpuGroup = _hostGpuGroupsDao.findByHostIdGroupName(hostId, entry.getKey()); + HostGpuGroupsVO gpuGroup = hostGpuGroupsDao.findByHostIdGroupName(hostId, entry.getKey()); HashMap values = entry.getValue(); Iterator> it2 = values.entrySet().iterator(); while (it2.hasNext()) { diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java new file mode 100644 index 000000000000..2628f1851f22 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDao.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.gpu.dao; + +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; + +import java.util.List; + +public interface VgpuProfileDao extends GenericDao { + + VgpuProfileVO findByNameAndCardId(String name, long cardId); + + int removeByCardId(long cardId); + + Pair, Integer> searchAndCountVgpuProfiles(Long id, String name, String keyword, Long gpuCardId, + boolean activeOnly, Long startIndex, Long pageSize); +} diff --git a/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java new file mode 100644 index 000000000000..11dd7edb30d5 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/gpu/dao/VgpuProfileDaoImpl.java @@ -0,0 +1,110 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.gpu.dao; + +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.List; + +@Component +public class VgpuProfileDaoImpl extends GenericDaoBase implements VgpuProfileDao { + + private final SearchBuilder allFieldSearch; + + @Inject + private GpuDeviceDao gpuDeviceDao; + + public VgpuProfileDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ); + allFieldSearch.and("cardId", allFieldSearch.entity().getCardId(), SearchCriteria.Op.IN); + allFieldSearch.done(); + } + + @Override + public VgpuProfileVO findByNameAndCardId(String name, long cardId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("name", name); + sc.setParameters("cardId", cardId); + return findOneBy(sc); + } + + @Override + public int removeByCardId(long cardId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("cardId", cardId); + return remove(sc); + } + + @Override + public Pair, Integer> searchAndCountVgpuProfiles(Long id, String name, String keyword, + Long gpuCardId, boolean activeOnly, Long startIndex, Long pageSize) { + Filter searchFilter = new Filter(VgpuProfileVO.class, "id", true, startIndex, pageSize); + SearchBuilder sb = createSearchBuilder(); + + if (id != null) { + sb.and("id", sb.entity().getId(), SearchCriteria.Op.EQ); + } + if (name != null) { + sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); + } + if (keyword != null) { + sb.and("keywordName", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.and("keywordDescription", sb.entity().getDescription(), SearchCriteria.Op.LIKE); + } + if (gpuCardId != null) { + sb.and("cardId", sb.entity().getCardId(), SearchCriteria.Op.EQ); + } + if (activeOnly) { + sb.and("ids", sb.entity().getId(), SearchCriteria.Op.IN); + } + sb.done(); + + // Build search criteria + SearchCriteria sc = sb.create(); + if (id != null) { + sc.setParameters("id", id); + } + if (name != null) { + sc.setParameters("name", name); + } + if (keyword != null) { + sc.setParameters("keywordName", "%" + keyword + "%"); + sc.setParameters("keywordDescription", "%" + keyword + "%"); + } + if (gpuCardId != null) { + sc.setParameters("cardId", gpuCardId); + } + + if (activeOnly) { + List vgpuProfileIds = gpuDeviceDao.getDistinctVgpuProfileIds(); + if (vgpuProfileIds.isEmpty()) { + return new Pair<>(List.of(), 0); + } + sc.setParameters("ids", vgpuProfileIds.toArray()); + } + + return searchAndCount(sc, searchFilter); + } +} diff --git a/engine/schema/src/main/java/com/cloud/host/HostVO.java b/engine/schema/src/main/java/com/cloud/host/HostVO.java index a449eb450cf9..468caf1e2271 100644 --- a/engine/schema/src/main/java/com/cloud/host/HostVO.java +++ b/engine/schema/src/main/java/com/cloud/host/HostVO.java @@ -45,7 +45,7 @@ import com.cloud.cpu.CPU; import org.apache.cloudstack.util.CPUArchConverter; import org.apache.cloudstack.util.HypervisorTypeConverter; -import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper; +import org.apache.cloudstack.utils.jsinterpreter.GenericRuleHelper; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.BooleanUtils; @@ -165,6 +165,9 @@ public class HostVO implements Host { @Column(name = "uuid") private String uuid; + @Column(name = "storage_access_groups") + String storageAccessGroups; + // This is a delayed load value. If the value is null, // then this field has not been loaded yet. // Call host dao to load it. @@ -357,6 +360,15 @@ public Boolean getIsTagARule() { return isTagARule; } + @Override + public String getStorageAccessGroups() { + return storageAccessGroups; + } + + public void setStorageAccessGroups(String storageAccessGroups) { + this.storageAccessGroups = storageAccessGroups; + } + public HashMap> getGpuGroupDetails() { return groupDetails; } @@ -404,6 +416,9 @@ public void setGpuGroups(HashMap> groupDe @Column(name = "mgmt_server_id") private Long managementServerId; + @Column(name = "last_mgmt_server_id") + private Long lastManagementServerId; + @Column(name = "dom0_memory") private long dom0MinMemory; @@ -570,6 +585,10 @@ public void setManagementServerId(Long managementServerId) { this.managementServerId = managementServerId; } + public void setLastManagementServerId(Long lastManagementServerId) { + this.lastManagementServerId = lastManagementServerId; + } + @Override public long getLastPinged() { return lastPinged; @@ -639,6 +658,11 @@ public Long getManagementServerId() { return managementServerId; } + @Override + public Long getLastManagementServerId() { + return lastManagementServerId; + } + @Override public Date getDisconnectedOn() { return disconnectedOn; @@ -832,7 +856,8 @@ public boolean checkHostServiceOfferingTags(ServiceOffering serviceOffering) { } if (BooleanUtils.isTrue(this.getIsTagARule())) { - return TagAsRuleHelper.interpretTagAsRule(this.getHostTags().get(0), serviceOffering.getHostTag(), HostTagsDao.hostTagRuleExecutionTimeout.value()); + return GenericRuleHelper.interpretTagAsRule(this.getHostTags().get(0), serviceOffering.getHostTag(), HostTagsDao.hostTagRuleExecutionTimeout.value(), + HostTagsDao.hostTagRuleExecutionTimeout.key()); } if (StringUtils.isEmpty(serviceOffering.getHostTag())) { 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 57ac35d3eb1a..d8bdabc3dcbb 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 @@ -31,12 +31,18 @@ import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; import com.cloud.utils.fsm.StateDao; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.config.ConfigKey; /** * Data Access Object for server * */ public interface HostDao extends GenericDao, StateDao { + + ConfigKey guestOsRuleExecutionTimeout = new ConfigKey<>("Advanced", Long.class, "guest.os.rule.execution.timeout", "3000", "The maximum runtime, in milliseconds, " + + "to execute a guest OS rule; if it is reached, a timeout will happen.", true); + long countBy(long clusterId, ResourceState... states); Integer countAllByType(final Host.Type type); @@ -83,6 +89,10 @@ public interface HostDao extends GenericDao, StateDao findHypervisorHostInCluster(long clusterId); + List findHypervisorHostInPod(long podId); + + List findHypervisorHostInZone(long zoneId); + HostVO findAnyStateHypervisorHostInCluster(long clusterId); HostVO findOldestExistentHypervisorHostInCluster(long clusterId); @@ -95,10 +105,14 @@ public interface HostDao extends GenericDao, StateDao findByPodId(Long podId); + List findByPodId(Long podId, Type type); + List listIdsByPodId(Long podId); List findByClusterId(Long clusterId); + List findByClusterId(Long clusterId, Type type); + List listIdsByClusterId(Long clusterId); List listIdsForUpRouting(Long zoneId, Long podId, Long clusterId); @@ -107,6 +121,8 @@ public interface HostDao extends GenericDao, StateDao listIdsForUpEnabledByZoneAndHypervisor(Long zoneId, HypervisorType hypervisorType); + List findRoutingByClusterId(Long clusterId); + List findByClusterIdAndEncryptionSupport(Long clusterId); /** @@ -125,6 +141,8 @@ public interface HostDao extends GenericDao, StateDao listAllHostsByZoneAndHypervisorType(long zoneId, HypervisorType hypervisorType); + List listAllRoutingHostsByZoneAndHypervisorType(long zoneId, HypervisorType hypervisorType); + List listAllHostsThatHaveNoRuleTag(Host.Type type, Long clusterId, Long podId, Long dcId); HostVO findByPublicIp(String publicIp); @@ -166,12 +184,40 @@ public interface HostDao extends GenericDao, StateDao listHostsWithActiveVMs(long offeringId); + List listHostsByMsAndDc(long msId, long dcId); + + List listHostsByMsDcResourceState(long msId, long dcId, List excludedResourceStates); + + List listHostsByMs(long msId); + + List listHostsByMsResourceState(long msId, List excludedResourceStates); + + /** + * Count Hosts by given Management Server, Host and Hypervisor Types, + * and exclude Hosts with given Resource States. + * + * @param msId Management Server Id + * @param excludedResourceStates Resource States to be excluded + * @param hostTypes Host Types + * @param hypervisorTypes Hypervisor Types + * @return Hosts count + */ + int countHostsByMsResourceStateTypeAndHypervisorType(long msId, List excludedResourceStates, + List hostTypes, List hypervisorTypes); + /** - * Retrieves the number of hosts/agents this {@see ManagementServer} has responsibility over. - * @param msid the id of the {@see ManagementServer} - * @return the number of hosts/agents this {@see ManagementServer} has responsibility over + * Retrieves the host ids/agents this {@see ManagementServer} has responsibility over. + * @param msId the id of the {@see ManagementServer} + * @return the host ids/agents this {@see ManagementServer} has responsibility over */ - int countByMs(long msid); + List listByMs(long msId); + + /** + * Retrieves the last host ids/agents this {@see ManagementServer} has responsibility over. + * @param msId the id of the {@see ManagementServer} + * @return the last host ids/agents this {@see ManagementServer} has responsibility over + */ + List listByLastMs(long msId); /** * Retrieves the hypervisor versions of the hosts in the datacenter which are in Up state in ascending order @@ -181,7 +227,9 @@ public interface HostDao extends GenericDao, StateDao listOrderedHostsHypervisorVersionsInDatacenter(long datacenterId, HypervisorType hypervisorType); - List findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags); + List findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags); + + List findHostsWithGuestOsRulesThatDidNotMatchOsOfGuestVm(String templateGuestOSName); List findClustersThatMatchHostTagRule(String computeOfferingTags); @@ -190,7 +238,7 @@ public interface HostDao extends GenericDao, StateDao findHostIdsByZoneClusterResourceStateTypeAndHypervisorType(final Long zoneId, final Long clusterId, - final List resourceStates, final List types, + final Long msId, final List resourceStates, final List types, final List hypervisorTypes); List listDistinctHypervisorTypes(final Long zoneId); @@ -200,4 +248,8 @@ List findHostIdsByZoneClusterResourceStateTypeAndHypervisorType(final Long List listDistinctArchTypes(final Long clusterId); List listByIds(final List ids); + + Long findClusterIdByVolumeInfo(VolumeInfo volumeInfo); + + List listDistinctStorageAccessGroups(String name, String keyword); } 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 8c3604d352b8..15727d9d8e66 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 @@ -35,7 +35,11 @@ import javax.inject.Inject; import javax.persistence.TableGenerator; -import org.apache.cloudstack.utils.jsinterpreter.TagAsRuleHelper; +import com.cloud.vm.VirtualMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.config.Configurable; +import org.apache.cloudstack.utils.jsinterpreter.GenericRuleHelper; import org.apache.commons.collections.CollectionUtils; import com.cloud.agent.api.VgpuTypesInfo; @@ -70,6 +74,7 @@ import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.JoinBuilder.JoinType; +import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; @@ -77,10 +82,11 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.UpdateBuilder; import com.cloud.utils.exception.CloudRuntimeException; +import org.apache.commons.lang3.ObjectUtils; @DB @TableGenerator(name = "host_req_sq", table = "op_host", pkColumnName = "id", valueColumnName = "sequence", allocationSize = 1) -public class HostDaoImpl extends GenericDaoBase implements HostDao { //FIXME: , ExternalIdDao { +public class HostDaoImpl extends GenericDaoBase implements HostDao, Configurable { private static final String LIST_HOST_IDS_BY_HOST_TAGS = "SELECT filtered.host_id, COUNT(filtered.tag) AS tag_count " + "FROM (SELECT host_id, tag, is_tag_a_rule FROM host_tags GROUP BY host_id,tag,is_tag_a_rule) AS filtered " @@ -104,7 +110,7 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao protected SearchBuilder IdStatusSearch; protected SearchBuilder TypeDcSearch; protected SearchBuilder TypeDcStatusSearch; - protected SearchBuilder TypeClusterStatusSearch; + protected SearchBuilder TypeStatusStateSearch; protected SearchBuilder MsStatusSearch; protected SearchBuilder DcPrivateIpAddressSearch; protected SearchBuilder DcStorageIpAddressSearch; @@ -127,8 +133,11 @@ public class HostDaoImpl extends GenericDaoBase implements HostDao protected SearchBuilder UnmanagedApplianceSearch; protected SearchBuilder MaintenanceCountSearch; protected SearchBuilder HostTypeCountSearch; + protected SearchBuilder ResponsibleMsSearch; + protected SearchBuilder ResponsibleMsDcSearch; + protected GenericSearchBuilder ResponsibleMsIdSearch; + protected GenericSearchBuilder LastMsIdSearch; protected SearchBuilder HostTypeClusterCountSearch; - protected SearchBuilder ResponsibleMsCountSearch; protected SearchBuilder HostTypeZoneCountSearch; protected SearchBuilder ClusterStatusSearch; protected SearchBuilder TypeNameZoneSearch; @@ -194,9 +203,24 @@ public void init() { HostTypeCountSearch.and("resourceState", HostTypeCountSearch.entity().getResourceState(), SearchCriteria.Op.EQ); HostTypeCountSearch.done(); - ResponsibleMsCountSearch = createSearchBuilder(); - ResponsibleMsCountSearch.and("managementServerId", ResponsibleMsCountSearch.entity().getManagementServerId(), SearchCriteria.Op.EQ); - ResponsibleMsCountSearch.done(); + ResponsibleMsSearch = createSearchBuilder(); + ResponsibleMsSearch.and("managementServerId", ResponsibleMsSearch.entity().getManagementServerId(), SearchCriteria.Op.EQ); + ResponsibleMsSearch.done(); + + ResponsibleMsDcSearch = createSearchBuilder(); + ResponsibleMsDcSearch.and("managementServerId", ResponsibleMsDcSearch.entity().getManagementServerId(), SearchCriteria.Op.EQ); + ResponsibleMsDcSearch.and("dcId", ResponsibleMsDcSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + ResponsibleMsDcSearch.done(); + + ResponsibleMsIdSearch = createSearchBuilder(String.class); + ResponsibleMsIdSearch.selectFields(ResponsibleMsIdSearch.entity().getUuid()); + ResponsibleMsIdSearch.and("managementServerId", ResponsibleMsIdSearch.entity().getManagementServerId(), SearchCriteria.Op.EQ); + ResponsibleMsIdSearch.done(); + + LastMsIdSearch = createSearchBuilder(String.class); + LastMsIdSearch.selectFields(LastMsIdSearch.entity().getUuid()); + LastMsIdSearch.and("lastManagementServerId", LastMsIdSearch.entity().getLastManagementServerId(), SearchCriteria.Op.EQ); + LastMsIdSearch.done(); HostTypeClusterCountSearch = createSearchBuilder(); HostTypeClusterCountSearch.and("cluster", HostTypeClusterCountSearch.entity().getClusterId(), SearchCriteria.Op.EQ); @@ -245,12 +269,14 @@ public void init() { TypeDcStatusSearch.and("resourceState", TypeDcStatusSearch.entity().getResourceState(), SearchCriteria.Op.EQ); TypeDcStatusSearch.done(); - TypeClusterStatusSearch = createSearchBuilder(); - TypeClusterStatusSearch.and("type", TypeClusterStatusSearch.entity().getType(), SearchCriteria.Op.EQ); - TypeClusterStatusSearch.and("cluster", TypeClusterStatusSearch.entity().getClusterId(), SearchCriteria.Op.EQ); - TypeClusterStatusSearch.and("status", TypeClusterStatusSearch.entity().getStatus(), SearchCriteria.Op.EQ); - TypeClusterStatusSearch.and("resourceState", TypeClusterStatusSearch.entity().getResourceState(), SearchCriteria.Op.EQ); - TypeClusterStatusSearch.done(); + TypeStatusStateSearch = createSearchBuilder(); + TypeStatusStateSearch.and("type", TypeStatusStateSearch.entity().getType(), SearchCriteria.Op.EQ); + TypeStatusStateSearch.and("cluster", TypeStatusStateSearch.entity().getClusterId(), SearchCriteria.Op.EQ); + TypeStatusStateSearch.and("pod", TypeStatusStateSearch.entity().getPodId(), SearchCriteria.Op.EQ); + TypeStatusStateSearch.and("zone", TypeStatusStateSearch.entity().getDataCenterId(), SearchCriteria.Op.EQ); + TypeStatusStateSearch.and("status", TypeStatusStateSearch.entity().getStatus(), SearchCriteria.Op.EQ); + TypeStatusStateSearch.and("resourceState", TypeStatusStateSearch.entity().getResourceState(), SearchCriteria.Op.EQ); + TypeStatusStateSearch.done(); IdsSearch = createSearchBuilder(); IdsSearch.and("id", IdsSearch.entity().getId(), SearchCriteria.Op.IN); @@ -307,10 +333,12 @@ public void init() { PodSearch = createSearchBuilder(); PodSearch.and("podId", PodSearch.entity().getPodId(), SearchCriteria.Op.EQ); + PodSearch.and("type", PodSearch.entity().getType(), Op.EQ); PodSearch.done(); ClusterSearch = createSearchBuilder(); ClusterSearch.and("clusterId", ClusterSearch.entity().getClusterId(), SearchCriteria.Op.EQ); + ClusterSearch.and("type", ClusterSearch.entity().getType(), Op.EQ); ClusterSearch.done(); TypeSearch = createSearchBuilder(); @@ -618,16 +646,22 @@ private void resetHosts(long managementServerId, long lastPingSecondsAfter) { sc.setParameters("lastPinged", lastPingSecondsAfter); sc.setParameters("status", Status.Disconnected, Status.Down, Status.Alert); - StringBuilder sb = new StringBuilder(); - List hosts = lockRows(sc, null, true); // exclusive lock - for (HostVO host : hosts) { - host.setManagementServerId(null); - update(host.getId(), host); - sb.append(host.getId()); - sb.append(" "); + // SELECT before bulk UPDATE to preserve per-host-ID trace logging — the bulk UPDATE + // cannot return which rows it matched since the WHERE column is being set to NULL + if (logger.isTraceEnabled()) { + List hosts = listBy(sc); + StringBuilder sb = new StringBuilder(); + for (HostVO host : hosts) { + sb.append(host.getId()); + sb.append(" "); + } + logger.trace("Following hosts will be reset: {}", sb); } - logger.trace("Following hosts got reset: {}", sb); + HostVO host = createForUpdate(); + host.setManagementServerId(null); + UpdateBuilder ub = getUpdateBuilder(host); + update(ub, sc, null); } /* @@ -1217,8 +1251,16 @@ public List listIdsByDataCenterId(Long zoneId) { @Override public List findByPodId(Long podId) { + return findByPodId(podId, null); + } + + @Override + public List findByPodId(Long podId, Type type) { SearchCriteria sc = PodSearch.create(); sc.setParameters("podId", podId); + if (type != null) { + sc.setParameters("type", Type.Routing); + } return listBy(sc); } @@ -1229,8 +1271,16 @@ public List listIdsByPodId(Long podId) { @Override public List findByClusterId(Long clusterId) { + return findByClusterId(clusterId, null); + } + + @Override + public List findByClusterId(Long clusterId, Type type) { SearchCriteria sc = ClusterSearch.create(); sc.setParameters("clusterId", clusterId); + if (type != null) { + sc.setParameters("type", Type.Routing); + } return listBy(sc); } @@ -1291,6 +1341,14 @@ public List listIdsForUpEnabledByZoneAndHypervisor(Long zoneId, Hypervisor return listIdsBy(null, Status.Up, ResourceState.Enabled, hypervisorType, zoneId, null, null); } + @Override + public List findRoutingByClusterId(Long clusterId) { + SearchCriteria sc = ClusterSearch.create(); + sc.setParameters("clusterId", clusterId); + sc.setParameters("type", Type.Routing); + return listBy(sc); + } + @Override public List findByClusterIdAndEncryptionSupport(Long clusterId) { SearchBuilder hostCapabilitySearch = _detailsDao.createSearchBuilder(); @@ -1334,7 +1392,7 @@ public HostVO findByIp(final String ipAddress) { @Override public List findHypervisorHostInCluster(long clusterId) { - SearchCriteria sc = TypeClusterStatusSearch.create(); + SearchCriteria sc = TypeStatusStateSearch.create(); sc.setParameters("type", Host.Type.Routing); sc.setParameters("cluster", clusterId); sc.setParameters("status", Status.Up); @@ -1343,9 +1401,31 @@ public List findHypervisorHostInCluster(long clusterId) { return listBy(sc); } + @Override + public List findHypervisorHostInZone(long zoneId) { + SearchCriteria sc = TypeStatusStateSearch.create(); + sc.setParameters("type", Host.Type.Routing); + sc.setParameters("zone", zoneId); + sc.setParameters("status", Status.Up); + sc.setParameters("resourceState", ResourceState.Enabled); + + return listBy(sc); + } + + @Override + public List findHypervisorHostInPod(long podId) { + SearchCriteria sc = TypeStatusStateSearch.create(); + sc.setParameters("type", Host.Type.Routing); + sc.setParameters("pod", podId); + sc.setParameters("status", Status.Up); + sc.setParameters("resourceState", ResourceState.Enabled); + + return listBy(sc); + } + @Override public HostVO findAnyStateHypervisorHostInCluster(long clusterId) { - SearchCriteria sc = TypeClusterStatusSearch.create(); + SearchCriteria sc = TypeStatusStateSearch.create(); sc.setParameters("type", Host.Type.Routing); sc.setParameters("cluster", clusterId); List list = listBy(sc, new Filter(1, true)); @@ -1354,7 +1434,7 @@ public HostVO findAnyStateHypervisorHostInCluster(long clusterId) { @Override public HostVO findOldestExistentHypervisorHostInCluster(long clusterId) { - SearchCriteria sc = TypeClusterStatusSearch.create(); + SearchCriteria sc = TypeStatusStateSearch.create(); sc.setParameters("type", Host.Type.Routing); sc.setParameters("cluster", clusterId); sc.setParameters("status", Status.Up); @@ -1384,6 +1464,16 @@ public List listAllHostsByZoneAndHypervisorType(long zoneId, HypervisorT return listBy(sc); } + @Override + public List listAllRoutingHostsByZoneAndHypervisorType(long zoneId, HypervisorType hypervisorType) { + SearchCriteria sc = DcSearch.create(); + sc.setParameters("dc", zoneId); + sc.setParameters("hypervisorType", hypervisorType.toString()); + sc.setParameters("type", Type.Routing); + + return listBy(sc); + } + @Override public List listAllHostsThatHaveNoRuleTag(Type type, Long clusterId, Long podId, Long dcId) { SearchCriteria sc = searchBuilderFindByIdTypeClusterIdPodIdDcIdAndWithoutRuleTag.create(); @@ -1456,11 +1546,13 @@ private List findHostIdsByHostTags(String hostTags){ } } - public List findHostsWithTagRuleThatMatchComputeOferringTags(String computeOfferingTags) { + @Override + public List findHostsWithTagRuleThatMatchComputeOfferingTags(String computeOfferingTags) { List hostTagVOList = _hostTagsDao.findHostRuleTags(); List result = new ArrayList<>(); for (HostTagVO rule: hostTagVOList) { - if (TagAsRuleHelper.interpretTagAsRule(rule.getTag(), computeOfferingTags, HostTagsDao.hostTagRuleExecutionTimeout.value())) { + if (GenericRuleHelper.interpretTagAsRule(rule.getTag(), computeOfferingTags, HostTagsDao.hostTagRuleExecutionTimeout.value(), + HostTagsDao.hostTagRuleExecutionTimeout.key())) { result.add(findById(rule.getHostId())); } } @@ -1468,9 +1560,26 @@ public List findHostsWithTagRuleThatMatchComputeOferringTags(String comp return result; } + @Override + public List findHostsWithGuestOsRulesThatDidNotMatchOsOfGuestVm(String templateGuestOSName) { + List hostIdsWithGuestOsRule = _detailsDao.findByName(Host.GUEST_OS_RULE); + List hostsWithIncompatibleRules = new ArrayList<>(); + for (DetailVO guestOsRule : hostIdsWithGuestOsRule) { + if (!GenericRuleHelper.interpretGuestOsRule(guestOsRule.getValue(), templateGuestOSName, HostDao.guestOsRuleExecutionTimeout.value(), + HostDao.guestOsRuleExecutionTimeout.key())) { + logger.trace("The guest OS rule [{}] of the host with ID [{}] is incompatible with the OS of the VM.", + guestOsRule.getHostId(), guestOsRule.getValue()); + hostsWithIncompatibleRules.add(findById(guestOsRule.getHostId())); + } + } + logger.trace("The hosts with the following IDs [{}] are incompatible with the VM considering their guest OS rule.", + hostsWithIncompatibleRules); + return hostsWithIncompatibleRules; + } + 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()); } @@ -1530,10 +1639,72 @@ public List listHostsWithActiveVMs(long offeringId) { } @Override - public int countByMs(long msid) { - SearchCriteria sc = ResponsibleMsCountSearch.create(); - sc.setParameters("managementServerId", msid); - return getCount(sc); + public List listHostsByMsAndDc(long msId, long dcId) { + SearchCriteria sc = ResponsibleMsDcSearch.create(); + sc.setParameters("managementServerId", msId); + sc.setParameters("dcId", dcId); + return listBy(sc); + } + + @Override + public List listHostsByMsDcResourceState(long msId, long dcId, List excludedResourceStates) { + QueryBuilder sc = QueryBuilder.create(HostVO.class); + sc.and(sc.entity().getManagementServerId(), Op.EQ, msId); + sc.and(sc.entity().getDataCenterId(), Op.EQ, dcId); + if (CollectionUtils.isNotEmpty(excludedResourceStates)) { + sc.and(sc.entity().getResourceState(), Op.NIN, excludedResourceStates.toArray()); + } + return listBy(sc.create()); + } + + @Override + public List listHostsByMs(long msId) { + SearchCriteria sc = ResponsibleMsSearch.create(); + sc.setParameters("managementServerId", msId); + return listBy(sc); + } + + @Override + public List listHostsByMsResourceState(long msId, List excludedResourceStates) { + QueryBuilder sc = QueryBuilder.create(HostVO.class); + sc.and(sc.entity().getManagementServerId(), Op.EQ, msId); + if (CollectionUtils.isNotEmpty(excludedResourceStates)) { + sc.and(sc.entity().getResourceState(), Op.NIN, excludedResourceStates.toArray()); + } + return listBy(sc.create()); + } + + @Override + public int countHostsByMsResourceStateTypeAndHypervisorType(long msId, + List excludedResourceStates, + List hostTypes, + List hypervisorTypes) { + QueryBuilder sc = QueryBuilder.create(HostVO.class); + sc.and(sc.entity().getManagementServerId(), Op.EQ, msId); + if (CollectionUtils.isNotEmpty(excludedResourceStates)) { + sc.and(sc.entity().getResourceState(), Op.NIN, excludedResourceStates.toArray()); + } + if (CollectionUtils.isNotEmpty(hostTypes)) { + sc.and(sc.entity().getType(), Op.IN, hostTypes.toArray()); + } + if (CollectionUtils.isNotEmpty(hypervisorTypes)) { + sc.and(sc.entity().getHypervisorType(), Op.IN, hypervisorTypes.toArray()); + } + return getCount(sc.create()); + } + + @Override + public List listByMs(long msId) { + SearchCriteria sc = ResponsibleMsIdSearch.create(); + sc.addAnd("managementServerId", SearchCriteria.Op.EQ, msId); + return customSearch(sc, null); + } + + @Override + public List listByLastMs(long msId) { + SearchCriteria sc = LastMsIdSearch.create(); + sc.addAnd("lastManagementServerId", SearchCriteria.Op.EQ, msId); + return customSearch(sc, null); } @Override @@ -1712,13 +1883,15 @@ public boolean isHostUp(long hostId) { } @Override - public List findHostIdsByZoneClusterResourceStateTypeAndHypervisorType(final Long zoneId, final Long clusterId, + public List findHostIdsByZoneClusterResourceStateTypeAndHypervisorType(final Long zoneId, + final Long clusterId, final Long managementServerId, final List resourceStates, final List types, final List hypervisorTypes) { GenericSearchBuilder sb = createSearchBuilder(Long.class); sb.selectFields(sb.entity().getId()); sb.and("zoneId", sb.entity().getDataCenterId(), SearchCriteria.Op.EQ); sb.and("clusterId", sb.entity().getClusterId(), SearchCriteria.Op.EQ); + sb.and("msId", sb.entity().getManagementServerId(), SearchCriteria.Op.EQ); sb.and("resourceState", sb.entity().getResourceState(), SearchCriteria.Op.IN); sb.and("type", sb.entity().getType(), SearchCriteria.Op.IN); if (CollectionUtils.isNotEmpty(hypervisorTypes)) { @@ -1734,6 +1907,9 @@ public List findHostIdsByZoneClusterResourceStateTypeAndHypervisorType(fin if (clusterId != null) { sc.setParameters("clusterId", clusterId); } + if (managementServerId != null) { + sc.setParameters("msId", managementServerId); + } if (CollectionUtils.isNotEmpty(hypervisorTypes)) { sc.setParameters("hypervisorTypes", hypervisorTypes.toArray()); } @@ -1801,4 +1977,66 @@ public List listByIds(List ids) { sc.setParameters("id", ids.toArray()); return search(sc, null); } + + + @Override + public Long findClusterIdByVolumeInfo(VolumeInfo volumeInfo) { + VirtualMachine virtualMachine = volumeInfo.getAttachedVM(); + if (virtualMachine == null) { + return null; + } + + Long hostId = ObjectUtils.defaultIfNull(virtualMachine.getHostId(), virtualMachine.getLastHostId()); + Host host = findById(hostId); + + if (host == null) { + logger.warn(String.format("VM [%s] has null host on DB, either this VM was never started, or there is some inconsistency on the DB.", virtualMachine.getUuid())); + return null; + } + + return host.getClusterId(); + } + + + @Override + public List listDistinctStorageAccessGroups(String name, String keyword) { + GenericSearchBuilder searchBuilder = createSearchBuilder(String.class); + + searchBuilder.select(null, SearchCriteria.Func.DISTINCT, searchBuilder.entity().getStorageAccessGroups()); + if (name != null) { + searchBuilder.and().op("storageAccessGroupExact", searchBuilder.entity().getStorageAccessGroups(), Op.EQ); + searchBuilder.or("storageAccessGroupPrefix", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.or("storageAccessGroupSuffix", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.or("storageAccessGroupMiddle", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + searchBuilder.cp(); + } + if (keyword != null) { + searchBuilder.and("keyword", searchBuilder.entity().getStorageAccessGroups(), Op.LIKE); + } + searchBuilder.done(); + + SearchCriteria sc = searchBuilder.create(); + if (name != null) { + sc.setParameters("storageAccessGroupExact", name); + sc.setParameters("storageAccessGroupPrefix", name + ",%"); + sc.setParameters("storageAccessGroupSuffix", "%," + name); + sc.setParameters("storageAccessGroupMiddle", "%," + name + ",%"); + } + + if (keyword != null) { + sc.setParameters("keyword", "%" + keyword + "%"); + } + + return customSearch(sc, null); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] {guestOsRuleExecutionTimeout}; + } + + @Override + public String getConfigComponentName() { + return HostDaoImpl.class.getSimpleName(); + } } diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDao.java index 8dc4efa91f3c..b72ec5239ffb 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDao.java @@ -32,4 +32,9 @@ public interface HostDetailsDao extends GenericDao { void deleteDetails(long hostId); List findByName(String name); + + void removeExternalDetails(long hostId); + + void replaceExternalDetails(long hostId, Map details); + } diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDaoImpl.java index 9c1340592f93..0f8e8623506d 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostDetailsDaoImpl.java @@ -18,6 +18,7 @@ import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -31,12 +32,14 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; +import com.cloud.vm.VmDetailConstants; @Component public class HostDetailsDaoImpl extends GenericDaoBase implements HostDetailsDao { protected final SearchBuilder HostSearch; protected final SearchBuilder DetailSearch; protected final SearchBuilder DetailNameSearch; + protected final SearchBuilder ExternalDetailSearch; public HostDetailsDaoImpl() { HostSearch = createSearchBuilder(); @@ -51,6 +54,11 @@ public HostDetailsDaoImpl() { DetailNameSearch = createSearchBuilder(); DetailNameSearch.and("name", DetailNameSearch.entity().getName(), SearchCriteria.Op.EQ); DetailNameSearch.done(); + + ExternalDetailSearch = createSearchBuilder(); + ExternalDetailSearch.and("hostId", ExternalDetailSearch.entity().getHostId(), SearchCriteria.Op.EQ); + ExternalDetailSearch.and("name", ExternalDetailSearch.entity().getName(), SearchCriteria.Op.LIKE); + ExternalDetailSearch.done(); } @Override @@ -130,4 +138,41 @@ public List findByName(String name) { sc.setParameters("name", name); return listBy(sc); } + + @Override + public void removeExternalDetails(long hostId) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + SearchCriteria sc = ExternalDetailSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("name", VmDetailConstants.EXTERNAL_DETAIL_PREFIX + "%"); + remove(sc); + txn.commit(); + } + + @Override + public void replaceExternalDetails(long hostId, Map details) { + if (details.isEmpty()) { + return; + } + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + List detailVOs = new ArrayList<>(); + for (Map.Entry entry : details.entrySet()) { + String name = entry.getKey(); + String value = entry.getValue(); + if ("password".equals(entry.getKey())) { + value = DBEncryptionUtil.encrypt(value); + } + detailVOs.add(new DetailVO(hostId, name, value)); + } + SearchCriteria sc = ExternalDetailSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("name", VmDetailConstants.EXTERNAL_DETAIL_PREFIX + "%"); + remove(sc); + for (DetailVO detail : detailVOs) { + persist(detail); + } + txn.commit(); + } } diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDao.java b/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDao.java index 7a00829fd44e..0d86ca0e48c7 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDao.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDao.java @@ -45,4 +45,9 @@ public interface HostTagsDao extends GenericDao { HostTagResponse newHostTagResponse(HostTagVO hostTag); List searchByIds(Long... hostTagIds); + + /** + * List all host tags defined on hosts within a cluster + */ + List listByClusterId(Long clusterId); } diff --git a/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java b/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java index 4aa14a31cfcf..d3fee6a26761 100644 --- a/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/host/dao/HostTagsDaoImpl.java @@ -23,6 +23,7 @@ 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.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Component; @@ -43,9 +44,12 @@ public class HostTagsDaoImpl extends GenericDaoBase implements private final SearchBuilder stSearch; private final SearchBuilder tagIdsearch; private final SearchBuilder ImplicitTagsSearch; + private final GenericSearchBuilder tagSearch; @Inject private ConfigurationDao _configDao; + @Inject + private HostDao hostDao; public HostTagsDaoImpl() { HostSearch = createSearchBuilder(); @@ -72,6 +76,11 @@ public HostTagsDaoImpl() { ImplicitTagsSearch.and("hostId", ImplicitTagsSearch.entity().getHostId(), SearchCriteria.Op.EQ); ImplicitTagsSearch.and("isImplicit", ImplicitTagsSearch.entity().getIsImplicit(), SearchCriteria.Op.EQ); ImplicitTagsSearch.done(); + + tagSearch = createSearchBuilder(String.class); + tagSearch.selectFields(tagSearch.entity().getTag()); + tagSearch.and("hostIdIN", tagSearch.entity().getHostId(), SearchCriteria.Op.IN); + tagSearch.done(); } @Override @@ -235,4 +244,15 @@ public List searchByIds(Long... tagIds) { return tagList; } + + @Override + public List listByClusterId(Long clusterId) { + List hostIds = hostDao.listIdsByClusterId(clusterId); + if (CollectionUtils.isEmpty(hostIds)) { + return new ArrayList<>(); + } + SearchCriteria sc = tagSearch.create(); + sc.setParameters("hostIdIN", hostIds.toArray()); + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java b/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java index ee5f67b09cd1..23f0b0f15ae3 100644 --- a/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/network/LBHealthCheckPolicyVO.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.network; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; @@ -25,8 +26,11 @@ import javax.persistence.Id; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import com.cloud.network.rules.HealthCheckPolicy; +import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @Entity @@ -68,6 +72,10 @@ public class LBHealthCheckPolicyVO implements HealthCheckPolicy { @Column(name = "display", updatable = true, nullable = false) protected boolean display = true; + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + protected LBHealthCheckPolicyVO() { this.uuid = UUID.randomUUID().toString(); } diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDao.java b/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDao.java index 4c9df8e749fe..372dc930b683 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDao.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDao.java @@ -24,6 +24,7 @@ import com.cloud.utils.db.GenericDao; public interface CounterDao extends GenericDao { + CounterVO findByNameProviderValue(String name, String value, String provider); public List listCounters(Long id, String name, String source, String provider, String keyword, Filter filter); } diff --git a/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDaoImpl.java index 2d3906b83414..0badbe449acc 100644 --- a/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/as/dao/CounterDaoImpl.java @@ -32,6 +32,7 @@ @Component public class CounterDaoImpl extends GenericDaoBase implements CounterDao { final SearchBuilder AllFieldsSearch; + final SearchBuilder CounterValueSearch; protected CounterDaoImpl() { AllFieldsSearch = createSearchBuilder(); @@ -40,6 +41,21 @@ protected CounterDaoImpl() { AllFieldsSearch.and("source", AllFieldsSearch.entity().getSource(), Op.EQ); AllFieldsSearch.and("provider", AllFieldsSearch.entity().getProvider(), Op.EQ); AllFieldsSearch.done(); + + CounterValueSearch = createSearchBuilder(); + CounterValueSearch.and("name", CounterValueSearch.entity().getName(), Op.EQ); + CounterValueSearch.and("value", CounterValueSearch.entity().getValue(), Op.EQ); + CounterValueSearch.and("provider", CounterValueSearch.entity().getProvider(), Op.EQ); + CounterValueSearch.done(); + } + + @Override + public CounterVO findByNameProviderValue(String name, String value, String provider) { + SearchCriteria sc = CounterValueSearch.create(); + sc.setParameters("name", name); + sc.setParameters("value", value); + sc.setParameters("provider", provider); + return findOneBy(sc); } @Override diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java index b80ccd9cd1b9..3527ce84dcf6 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDao.java @@ -43,7 +43,9 @@ public interface FirewallRulesDao extends GenericDao { List listStaticNatByVmId(long vmId); - List listByIpPurposeAndProtocolAndNotRevoked(long ipAddressId, Integer startPort, Integer endPort, String protocol, FirewallRule.Purpose purpose); + List listByIpPurposePortsProtocolAndNotRevoked(long ipAddressId, Integer startPort, Integer endPort, String protocol, FirewallRule.Purpose purpose); + + List listByIpPurposeProtocolAndNotRevoked(long ipAddressId, FirewallRule.Purpose purpose, String protocol); FirewallRuleVO findByRelatedId(long ruleId); @@ -67,11 +69,15 @@ public interface FirewallRulesDao extends GenericDao { List listByNetworkPurposeTrafficType(long networkId, FirewallRule.Purpose purpose, FirewallRule.TrafficType trafficType); + List listByVpcPurposeTrafficType(long vpcId, FirewallRule.Purpose purpose, FirewallRule.TrafficType trafficType); + List listByIpAndPurposeWithState(Long addressId, FirewallRule.Purpose purpose, FirewallRule.State state); void loadSourceCidrs(FirewallRuleVO rule); void loadDestinationCidrs(FirewallRuleVO rule); + FirewallRuleVO findByNetworkIdAndPorts(long networkId, int startPort, int endPort); + List listRoutingIngressFirewallRules(long networkId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java index c8bd7e2147ea..5a1e1aae6b66 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/FirewallRulesDaoImpl.java @@ -48,6 +48,7 @@ public class FirewallRulesDaoImpl extends GenericDaoBase i protected final SearchBuilder NotRevokedSearch; protected final SearchBuilder ReleaseSearch; protected SearchBuilder VmSearch; + protected SearchBuilder FirewallByPortsAndNetwork; protected final SearchBuilder SystemRuleSearch; protected final GenericSearchBuilder RulesByIpCount; protected final SearchBuilder RoutingFirewallRulesSearch; @@ -73,6 +74,7 @@ protected FirewallRulesDaoImpl() { AllFieldsSearch.and("domain", AllFieldsSearch.entity().getDomainId(), Op.EQ); AllFieldsSearch.and("id", AllFieldsSearch.entity().getId(), Op.EQ); AllFieldsSearch.and("networkId", AllFieldsSearch.entity().getNetworkId(), Op.EQ); + AllFieldsSearch.and("vpcId", AllFieldsSearch.entity().getVpcId(), Op.EQ); AllFieldsSearch.and("related", AllFieldsSearch.entity().getRelated(), Op.EQ); AllFieldsSearch.and("trafficType", AllFieldsSearch.entity().getTrafficType(), Op.EQ); AllFieldsSearch.done(); @@ -93,6 +95,7 @@ protected FirewallRulesDaoImpl() { ReleaseSearch.and("ipId", ReleaseSearch.entity().getSourceIpAddressId(), Op.EQ); ReleaseSearch.and("purpose", ReleaseSearch.entity().getPurpose(), Op.EQ); ReleaseSearch.and("ports", ReleaseSearch.entity().getSourcePortStart(), Op.IN); + ReleaseSearch.and("removed", ReleaseSearch.entity().getRemoved(), Op.NULL); ReleaseSearch.done(); SystemRuleSearch = createSearchBuilder(); @@ -106,6 +109,12 @@ protected FirewallRulesDaoImpl() { RulesByIpCount.and("state", RulesByIpCount.entity().getState(), Op.EQ); RulesByIpCount.done(); + FirewallByPortsAndNetwork = createSearchBuilder(); + FirewallByPortsAndNetwork.and("networkId", FirewallByPortsAndNetwork.entity().getNetworkId(), Op.EQ); + FirewallByPortsAndNetwork.and("sourcePortStart", FirewallByPortsAndNetwork.entity().getSourcePortStart(), Op.EQ); + FirewallByPortsAndNetwork.and("sourcePortEnd", FirewallByPortsAndNetwork.entity().getSourcePortEnd(), Op.EQ); + FirewallByPortsAndNetwork.done(); + RoutingFirewallRulesSearch = createSearchBuilder(); RoutingFirewallRulesSearch.and("networkId", RoutingFirewallRulesSearch.entity().getNetworkId(), Op.EQ); RoutingFirewallRulesSearch.and("purpose", RoutingFirewallRulesSearch.entity().getPurpose(), Op.EQ); @@ -263,8 +272,25 @@ public void saveDestinationCidrs(FirewallRuleVO firewallRule, List cidrL } @Override - public List listByIpPurposeAndProtocolAndNotRevoked(long ipAddressId, Integer startPort, Integer endPort, String protocol, - FirewallRule.Purpose purpose) { + public List listByIpPurposeProtocolAndNotRevoked(long ipAddressId, Purpose purpose, String protocol) { + SearchCriteria sc = NotRevokedSearch.create(); + sc.setParameters("ipId", ipAddressId); + sc.setParameters("state", State.Revoke); + + if (purpose != null) { + sc.setParameters("purpose", purpose); + } + + if (protocol != null) { + sc.setParameters("protocol", protocol); + } + + return listBy(sc); + } + + @Override + public List listByIpPurposePortsProtocolAndNotRevoked(long ipAddressId, Integer startPort, Integer endPort, String protocol, + FirewallRule.Purpose purpose) { SearchCriteria sc = NotRevokedSearch.create(); sc.setParameters("ipId", ipAddressId); sc.setParameters("state", State.Revoke); @@ -331,6 +357,22 @@ public List listByNetworkPurposeTrafficType(long networkId, Purp return listBy(sc); } + @Override + public List listByVpcPurposeTrafficType(long vpcId, Purpose purpose, TrafficType trafficType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vpcId", vpcId); + + if (purpose != null) { + sc.setParameters("purpose", purpose); + } + + if (trafficType != null) { + sc.setParameters("trafficType", trafficType); + } + + return listBy(sc); + } + @Override @DB public boolean remove(Long id) { @@ -391,6 +433,16 @@ public void loadDestinationCidrs(FirewallRuleVO rule){ rule.setDestinationCidrsList(destCidrs); } + @Override + public FirewallRuleVO findByNetworkIdAndPorts(long networkId, int startPort, int endPort) { + SearchCriteria sc = FirewallByPortsAndNetwork.create(); + sc.setParameters("networkId", networkId); + sc.setParameters("sourcePortStart", startPort); + sc.setParameters("sourcePortEnd", endPort); + + return findOneBy(sc); + } + @Override public List listRoutingIngressFirewallRules(long networkId) { SearchCriteria sc = RoutingFirewallRulesSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java index 5499d04e3a1f..0a5ecd25667e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressDaoImpl.java @@ -197,6 +197,7 @@ public void unassignIpAddress(long ipAddressId) { address.setSourceNat(false); address.setOneToOneNat(false); address.setAssociatedWithVmId(null); + address.setForRouter(false); address.setState(State.Free); address.setAssociatedWithNetworkId(null); address.setVpcId(null); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java index 88e146d2a804..a3a65fdb01b3 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/IPAddressVO.java @@ -117,6 +117,9 @@ public class IPAddressVO implements IpAddress { @Column(name = "forsystemvms") private boolean forSystemVms = false; + @Column(name = "for_router") + private boolean forRouter = false; + @Column(name= GenericDao.REMOVED_COLUMN) private Date removed; @@ -388,4 +391,13 @@ public void setRuleState(State ruleState) { public boolean isForSystemVms() { return forSystemVms; } + + @Override + public boolean isForRouter() { + return forRouter; + } + + public void setForRouter(boolean forRouter) { + this.forRouter = forRouter; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java index 45927f3c3775..a9b4e02ba87f 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LBHealthCheckPolicyDaoImpl.java @@ -32,8 +32,9 @@ public class LBHealthCheckPolicyDaoImpl extends GenericDaoBase sc = createSearchCriteria(); sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); + sc.addAnd("removed", SearchCriteria.Op.NULL); - expunge(sc); + remove(sc); } @Override @@ -41,8 +42,9 @@ public void remove(long loadBalancerId, Boolean revoke) { SearchCriteria sc = createSearchCriteria(); sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke); + sc.addAnd("removed", SearchCriteria.Op.NULL); - expunge(sc); + remove(sc); } @Override diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java index 6669d70824a7..03fadbbd6598 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDao.java @@ -28,4 +28,6 @@ public interface LBStickinessPolicyDao extends GenericDao listByLoadBalancerIdAndDisplayFlag(long loadBalancerId, boolean forDisplay); List listByLoadBalancerId(long loadBalancerId, boolean revoke); + + List listByLoadBalancerId(long loadBalancerId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java index 717458fdb99f..86fd908b6b76 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyDaoImpl.java @@ -31,8 +31,9 @@ public class LBStickinessPolicyDaoImpl extends GenericDaoBase sc = createSearchCriteria(); sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); + sc.addAnd("removed", SearchCriteria.Op.NULL); - expunge(sc); + remove(sc); } @Override @@ -40,8 +41,9 @@ public void remove(long loadBalancerId, Boolean revoke) { SearchCriteria sc = createSearchCriteria(); sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke); + sc.addAnd("removed", SearchCriteria.Op.NULL); - expunge(sc); + remove(sc); } @Override @@ -62,4 +64,10 @@ public List listByLoadBalancerId(long loadBalancerId, bool return listBy(sc); } + @Override + public List listByLoadBalancerId(long loadBalancerId) { + SearchCriteria sc = createSearchCriteria(); + sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java index 72b8fc151b78..ae658f0757f0 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LBStickinessPolicyVO.java @@ -17,6 +17,7 @@ package com.cloud.network.dao; import java.util.ArrayList; +import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -30,9 +31,12 @@ import javax.persistence.Id; import javax.persistence.PrimaryKeyJoinColumn; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import com.cloud.network.rules.StickinessPolicy; import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @Entity @@ -68,6 +72,10 @@ public class LBStickinessPolicyVO implements StickinessPolicy { @Column(name = "display", updatable = true, nullable = false) protected boolean display = true; + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + protected LBStickinessPolicyVO() { this.uuid = UUID.randomUUID().toString(); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java index f95c61733f28..7fbe58df930c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerCertMapVO.java @@ -16,13 +16,17 @@ // under the License. package com.cloud.network.dao; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.api.InternalIdentity; @Entity @@ -45,6 +49,10 @@ public class LoadBalancerCertMapVO implements InternalIdentity { @Column(name = "revoke") private boolean revoke = false; + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + public LoadBalancerCertMapVO() { this.uuid = UUID.randomUUID().toString(); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java index dc37cdeefe3d..03a1e45d917e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapDaoImpl.java @@ -34,8 +34,9 @@ public class LoadBalancerVMMapDaoImpl extends GenericDaoBase sc = createSearchCriteria(); sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); + sc.addAnd("removed", SearchCriteria.Op.NULL); - expunge(sc); + remove(sc); } @Override @@ -43,11 +44,12 @@ public void remove(long loadBalancerId, List instanceIds, Boolean revoke) SearchCriteria sc = createSearchCriteria(); sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); sc.addAnd("instanceId", SearchCriteria.Op.IN, instanceIds.toArray()); + sc.addAnd("removed", SearchCriteria.Op.NULL); if (revoke != null) { sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke); } - expunge(sc); + remove(sc); } @Override @@ -56,12 +58,13 @@ public void remove(long loadBalancerId, long instanceId, String instanceIp, Bool sc.addAnd("loadBalancerId", SearchCriteria.Op.EQ, loadBalancerId); sc.addAnd("instanceId", SearchCriteria.Op.IN, instanceId); sc.addAnd("instanceIp", SearchCriteria.Op.EQ, instanceIp); + sc.addAnd("removed", SearchCriteria.Op.NULL); if (revoke != null) { sc.addAnd("revoke", SearchCriteria.Op.EQ, revoke); } - expunge(sc); + remove(sc); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java index 721349613d88..2c88bf9c5a60 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVMMapVO.java @@ -22,9 +22,14 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.api.InternalIdentity; +import java.util.Date; + @Entity @Table(name = "load_balancer_vm_map") public class LoadBalancerVMMapVO implements InternalIdentity { @@ -48,6 +53,10 @@ public class LoadBalancerVMMapVO implements InternalIdentity { @Column(name = "state") private String state; + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed = null; + public LoadBalancerVMMapVO() { } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java index ad0338b98497..3886529322e3 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/LoadBalancerVO.java @@ -144,4 +144,13 @@ public String toString() { ReflectionToStringBuilderUtils.reflectOnlySelectedFields( this, "id", "uuid", "name", "purpose", "state")); } + + /** + * Sets the CIDR list associated with this load balancer rule. + * + * @param cidrList a comma-separated list of CIDR strings, e.g. "1.2.3.4/24,1.2.3.5/24" or an empty string e.g. "" to clear the restrictions + */ + public void setCidrList(String cidrList) { + this.cidrList = cidrList; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDao.java new file mode 100644 index 000000000000..fe21f72e4dbb --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDao.java @@ -0,0 +1,24 @@ +// 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.network.dao; + +import com.cloud.network.element.NetrisProviderVO; +import com.cloud.utils.db.GenericDao; + +public interface NetrisProviderDao extends GenericDao { + NetrisProviderVO findByZoneId(long zoneId); +} diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDaoImpl.java new file mode 100644 index 000000000000..86ea04f1db09 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetrisProviderDaoImpl.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 com.cloud.network.dao; + +import com.cloud.network.element.NetrisProviderVO; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.springframework.stereotype.Component; + +@Component +@DB() +public class NetrisProviderDaoImpl extends GenericDaoBase implements NetrisProviderDao { + + final SearchBuilder allFieldsSearch; + + public NetrisProviderDaoImpl() { + super(); + allFieldsSearch = createSearchBuilder(); + allFieldsSearch.and("id", allFieldsSearch.entity().getId(), + SearchCriteria.Op.EQ); + allFieldsSearch.and("uuid", allFieldsSearch.entity().getUuid(), + SearchCriteria.Op.EQ); + allFieldsSearch.and("hostname", allFieldsSearch.entity().getUrl(), + SearchCriteria.Op.EQ); + allFieldsSearch.and("zone_id", allFieldsSearch.entity().getZoneId(), + SearchCriteria.Op.EQ); + allFieldsSearch.done(); + } + + @Override + public NetrisProviderVO findByZoneId(long zoneId) { + SearchCriteria sc = allFieldsSearch.create(); + sc.setParameters("zone_id", zoneId); + return findOneBy(sc); + } +} diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java index 0861a424ebb6..b656d896e37f 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDao.java @@ -18,11 +18,13 @@ import java.util.List; import java.util.Map; +import java.util.Set; import com.cloud.network.Network; import com.cloud.network.Network.GuestType; import com.cloud.network.Network.State; import com.cloud.network.Networks.TrafficType; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.fsm.StateDao; @@ -47,6 +49,12 @@ public interface NetworkDao extends GenericDao, StateDao listByNetworkDomains(Set uniqueNtwkDomains); + + List listByNetworkDomainsAndAccountIds(Set uniqueNtwkDomains, Set accountIds); + + List listByNetworkDomainsAndDomainIds(Set uniqueNtwkDomains, Set domainIds); + /** * Retrieves the next available mac address in this network configuration. * @@ -89,8 +97,13 @@ public interface NetworkDao extends GenericDao, StateDao serviceProviderMap); + List listByZoneAndTrafficType(long zoneId, TrafficType trafficType, Filter filter); + List listByZoneAndTrafficType(long zoneId, TrafficType trafficType); + List listByZonesTrafficTypeAndOwners(List zoneIds, final TrafficType trafficType, + List accountIds, List domainIds, Filter filter); + void setCheckForGc(long networkId); int getNetworkCountByNetworkOffId(long networkOfferingId); @@ -128,4 +141,6 @@ public interface NetworkDao extends GenericDao, StateDao listByPhysicalNetworkPvlan(long physicalNetworkId, String broadcastUri); List getAllPersistentNetworksFromZone(long dataCenterId); + + NetworkVO findByZoneIdAndAccountIdAndGuestTypeAndName(long zoneId, long accountId, GuestType guestType, String name); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java index 058ffa0030d8..fbf6a2ac2261 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkDaoImpl.java @@ -19,6 +19,7 @@ import java.net.URI; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -29,15 +30,14 @@ import javax.inject.Inject; import javax.persistence.TableGenerator; -import com.cloud.utils.exception.CloudRuntimeException; import org.apache.cloudstack.acl.ControlledEntity.ACLType; import org.apache.cloudstack.api.ApiConstants; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.network.Network; import com.cloud.network.Network.Event; import com.cloud.network.Network.GuestType; -import com.cloud.network.Network.Provider; import com.cloud.network.Network.Service; import com.cloud.network.Network.State; import com.cloud.network.Networks.BroadcastDomainType; @@ -63,6 +63,7 @@ import com.cloud.utils.db.SearchCriteria.Op; import com.cloud.utils.db.SequenceFetcher; import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.net.NetUtils; @Component @@ -86,6 +87,7 @@ public class NetworkDaoImpl extends GenericDaoBaseimplements Ne GenericSearchBuilder GarbageCollectedSearch; SearchBuilder PrivateNetworkSearch; + SearchBuilder NetworkDomainSearch; @Inject ResourceTagDao _tagsDao; @@ -192,12 +194,19 @@ protected void init() { PersistentNetworkSearch.and("id", PersistentNetworkSearch.entity().getId(), Op.NEQ); PersistentNetworkSearch.and("guestType", PersistentNetworkSearch.entity().getGuestType(), Op.IN); PersistentNetworkSearch.and("broadcastUri", PersistentNetworkSearch.entity().getBroadcastUri(), Op.EQ); + PersistentNetworkSearch.and("dc", PersistentNetworkSearch.entity().getDataCenterId(), Op.EQ); PersistentNetworkSearch.and("removed", PersistentNetworkSearch.entity().getRemoved(), Op.NULL); final SearchBuilder persistentNtwkOffJoin = _ntwkOffDao.createSearchBuilder(); persistentNtwkOffJoin.and("persistent", persistentNtwkOffJoin.entity().isPersistent(), Op.EQ); PersistentNetworkSearch.join("persistent", persistentNtwkOffJoin, PersistentNetworkSearch.entity().getNetworkOfferingId(), persistentNtwkOffJoin.entity().getId(), JoinType.INNER); PersistentNetworkSearch.done(); + NetworkDomainSearch = createSearchBuilder(); + NetworkDomainSearch.and("networkDomains", NetworkDomainSearch.entity().getNetworkDomain(), Op.IN); + NetworkDomainSearch.and("accounts", NetworkDomainSearch.entity().getAccountId(), Op.IN); + NetworkDomainSearch.and("domains", NetworkDomainSearch.entity().getDomainId(), Op.IN); + NetworkDomainSearch.done(); + PhysicalNetworkSearch = createSearchBuilder(); PhysicalNetworkSearch.and("physicalNetworkId", PhysicalNetworkSearch.entity().getPhysicalNetworkId(), Op.EQ); PhysicalNetworkSearch.done(); @@ -382,7 +391,7 @@ public void persistNetworkServiceProviders(final long networkId, final Map getAllPersistentNetworksFromZone(long dataCenterId) { return search(sc, null); } + @Override + public List listByNetworkDomains(Set uniqueNtwkDomains) { + SearchCriteria sc = NetworkDomainSearch.create(); + sc.setParameters("networkDomains", uniqueNtwkDomains.toArray()); + return search(sc, null); + } + + @Override + public List listByNetworkDomainsAndAccountIds(Set uniqueNtwkDomains, Set accountIds) { + SearchCriteria sc = NetworkDomainSearch.create(); + sc.setParameters("networkDomains", uniqueNtwkDomains.toArray()); + sc.setParameters("accounts", accountIds.toArray()); + return search(sc, null); + } + + @Override + public List listByNetworkDomainsAndDomainIds(Set uniqueNtwkDomains, Set domainIds) { + SearchCriteria sc = NetworkDomainSearch.create(); + sc.setParameters("networkDomains", uniqueNtwkDomains.toArray()); + sc.setParameters("domains", domainIds.toArray()); + return search(sc, null); + } + @Override public String getNextAvailableMacAddress(final long networkConfigId, Integer zoneMacIdentifier) { final SequenceFetcher fetch = SequenceFetcher.getInstance(); @@ -568,7 +600,7 @@ public List listByPhysicalNetwork(final long physicalNetworkId) { public List listByPhysicalNetworkTrafficType(final long physicalNetworkId, final TrafficType trafficType) { final SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("trafficType", trafficType); - sc.setParameters("physicalNetwork", physicalNetworkId); + sc.setParameters("physicalNetworkId", physicalNetworkId); return listBy(sc); } @@ -601,12 +633,46 @@ public List listBy(final long accountId, final long dataCenterId, fin } @Override - public List listByZoneAndTrafficType(final long zoneId, final TrafficType trafficType) { + public List listByZoneAndTrafficType(final long zoneId, final TrafficType trafficType, Filter filter) { final SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("datacenter", zoneId); sc.setParameters("trafficType", trafficType); - return listBy(sc, null); + return listBy(sc, filter); + } + + @Override + public List listByZoneAndTrafficType(final long zoneId, final TrafficType trafficType) { + return listByZoneAndTrafficType(zoneId, trafficType, null); + } + + @Override + public List listByZonesTrafficTypeAndOwners(List zoneIds, final TrafficType trafficType, + List accountIds, List domainIds, Filter filter) { + if (CollectionUtils.isEmpty(zoneIds)) { + return Collections.emptyList(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.IN); + sb.and("trafficType", sb.entity().getTrafficType(), Op.EQ); + boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds); + boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds); + if (accountIdsNotEmpty || domainIdsNotEmpty) { + sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN); + sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN); + sb.cp(); + } + sb.done(); + final SearchCriteria sc = sb.create(); + sc.setParameters("dataCenterId", zoneIds.toArray()); + sc.setParameters("trafficType", trafficType); + if (accountIdsNotEmpty) { + sc.setParameters("account", accountIds.toArray()); + } + if (domainIdsNotEmpty) { + sc.setParameters("domain", domainIds.toArray()); + } + return listBy(sc, filter); } @Override @@ -877,4 +943,16 @@ public List listByPhysicalNetworkPvlan(long physicalNetworkId, String return overlappingNetworks; } + + @Override + public NetworkVO findByZoneIdAndAccountIdAndGuestTypeAndName(long zoneId, long accountId, GuestType guestType, String name) { + SearchCriteria sc = AllFieldsSearch.create(); + + sc.setParameters("datacenter", zoneId); + sc.setParameters("account", accountId); + sc.setParameters("guestType", guestType); + sc.setParameters("name", name); + + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java b/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java index 4e1e9aff4561..65598eda7399 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/NetworkServiceMapVO.java @@ -81,6 +81,12 @@ public NetworkServiceMapVO(long networkId, Service service, Provider provider) { this.provider = provider.getName(); } + public NetworkServiceMapVO(long networkId, String serviceName, String providerName) { + this.networkId = networkId; + this.service = serviceName; + this.provider = providerName; + } + @Override public String toString() { StringBuilder buf = new StringBuilder("[Network Service["); 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/PhysicalNetworkServiceProviderVO.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java index 9557c7465bff..4be6c5bc344d 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkServiceProviderVO.java @@ -97,6 +97,9 @@ public class PhysicalNetworkServiceProviderVO implements PhysicalNetworkServiceP @Column(name = "networkacl_service_provided") boolean networkAclServiceProvided; + @Column(name = "custom_action_service_provided") + boolean customActionServiceProvided; + @Column(name = GenericDao.REMOVED_COLUMN) Date removed; @@ -278,6 +281,7 @@ public void setEnabledServices(List services) { this.setUserdataServiceProvided(services.contains(Service.UserData)); this.setSecuritygroupServiceProvided(services.contains(Service.SecurityGroup)); this.setNetworkAclServiceProvided(services.contains(Service.NetworkACL)); + this.setCustomActionServiceProvided(services.contains(Service.CustomAction)); } @Override @@ -301,21 +305,27 @@ public List getEnabledServices() { if (this.isLbServiceProvided()) { services.add(Service.Lb); } - if (this.sourcenatServiceProvided) { + if (this.isSourcenatServiceProvided()) { services.add(Service.SourceNat); } - if (this.staticnatServiceProvided) { + if (this.isStaticnatServiceProvided()) { services.add(Service.StaticNat); } - if (this.portForwardingServiceProvided) { + if (this.isPortForwardingServiceProvided()) { services.add(Service.PortForwarding); } + if (this.isNetworkAclServiceProvided()) { + services.add(Service.NetworkACL); + } if (this.isUserdataServiceProvided()) { services.add(Service.UserData); } if (this.isSecuritygroupServiceProvided()) { services.add(Service.SecurityGroup); } + if (this.isCustomActionServiceProvided()) { + services.add(Service.CustomAction); + } return services; } @@ -327,4 +337,12 @@ public boolean isNetworkAclServiceProvided() { public void setNetworkAclServiceProvided(boolean networkAclServiceProvided) { this.networkAclServiceProvided = networkAclServiceProvided; } + + public boolean isCustomActionServiceProvided() { + return customActionServiceProvided; + } + + public void setCustomActionServiceProvided(boolean customActionServiceProvided) { + this.customActionServiceProvided = customActionServiceProvided; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java index 6504bb1f3c8c..fdd827ffeeef 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/PhysicalNetworkTrafficTypeDaoImpl.java @@ -125,7 +125,7 @@ public String getNetworkTag(long physicalNetworkId, TrafficType trafficType, Hyp sc = simulatorAllFieldsSearch.create(); } else if (hType == HypervisorType.Ovm) { sc = ovmAllFieldsSearch.create(); - } else if (hType == HypervisorType.BareMetal) { + } else if (hType == HypervisorType.BareMetal || hType == HypervisorType.External) { return null; } else if (hType == HypervisorType.Hyperv) { sc = hypervAllFieldsSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java index 8113c06c866e..f4120a138ac5 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDao.java @@ -33,4 +33,6 @@ public interface RemoteAccessVpnDao extends GenericDao List findByAccount(Long accountId); List listByNetworkId(Long networkId); + + List listByVpcId(Long vpcId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java index 484aa6f6631e..ccbc60a5562c 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RemoteAccessVpnDaoImpl.java @@ -85,4 +85,11 @@ public List listByNetworkId(Long networkId) { sc.setParameters("networkId", networkId); return listBy(sc); } + + @Override + public List listByVpcId(Long vpcId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vpcId", vpcId); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java b/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java index 9803ccb6a4bd..204ef2d15381 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/RouterHealthCheckResultVO.java @@ -29,6 +29,7 @@ import javax.persistence.TemporalType; import com.cloud.network.RouterHealthCheckResult; +import com.cloud.network.VirtualNetworkApplianceService; import com.cloud.utils.StringUtils; @Entity @@ -49,7 +50,7 @@ public class RouterHealthCheckResultVO implements RouterHealthCheckResult { private String checkType; @Column(name = "check_result") - private boolean checkResult; + private VirtualNetworkApplianceService.RouterHealthStatus checkResult; @Temporal(TemporalType.TIMESTAMP) @Column(name = "last_update", updatable = true, nullable = true) @@ -87,7 +88,7 @@ public String getCheckType() { } @Override - public boolean getCheckResult() { + public VirtualNetworkApplianceService.RouterHealthStatus getCheckResult() { return checkResult; } @@ -105,7 +106,7 @@ public byte[] getCheckDetails() { return checkDetails; } - public void setCheckResult(boolean checkResult) { + public void setCheckResult(VirtualNetworkApplianceService.RouterHealthStatus checkResult) { this.checkResult = checkResult; } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java index d3fef252f506..3475003c2690 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDao.java @@ -20,4 +20,6 @@ public interface Site2SiteVpnGatewayDao extends GenericDao { Site2SiteVpnGatewayVO findByVpcId(long vpcId); + + Site2SiteVpnGatewayVO findByPublicIpAddress(long ipAddressId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java index d1fde963217e..0aeefe90c29e 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/Site2SiteVpnGatewayDaoImpl.java @@ -35,6 +35,7 @@ public class Site2SiteVpnGatewayDaoImpl extends GenericDaoBase sc = AllFieldsSearch.create(); + sc.setParameters("ipAddressId", ipAddressId); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/SslCertDao.java b/engine/schema/src/main/java/com/cloud/network/dao/SslCertDao.java index 80bb44a1f4a0..1e73cd7b33ee 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/SslCertDao.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/SslCertDao.java @@ -22,4 +22,6 @@ public interface SslCertDao extends GenericDao { List listByAccountId(Long id); + + int removeByAccountId(long accountId); } diff --git a/engine/schema/src/main/java/com/cloud/network/dao/SslCertDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/dao/SslCertDaoImpl.java index 185c18aecd85..efadc009dfc2 100644 --- a/engine/schema/src/main/java/com/cloud/network/dao/SslCertDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/dao/SslCertDaoImpl.java @@ -40,4 +40,10 @@ public List listByAccountId(Long accountId) { return listBy(sc); } + @Override + public int removeByAccountId(long accountId) { + SearchCriteria sc = listByAccountId.create(); + sc.setParameters("accountId", accountId); + return remove(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java b/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java new file mode 100644 index 000000000000..113678f7b010 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/network/element/NetrisProviderVO.java @@ -0,0 +1,265 @@ +// 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.network.element; + +import com.cloud.network.netris.NetrisProvider; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "netris_providers") +public class NetrisProviderVO implements NetrisProvider { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name; + + @Column(name = "zone_id") + private long zoneId; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "url") + private String url; + + @Column(name = "username") + private String username; + + @Column(name = "password") + private String password; + + @Column(name = "site_name") + private String siteName; + + @Column(name = "tenant_name") + private String tenantName; + + @Column(name = "netris_tag") + private String netrisTag; + + @Column(name = "created") + private Date created; + + @Column(name = "removed") + private Date removed; + + public NetrisProviderVO() { + this.uuid = UUID.randomUUID().toString(); + } + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public long getZoneId() { + return zoneId; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + @Override + public String getUrl() { + return url; + } + + public void setUrl(String url) { + this.url = url; + } + + @Override + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String getSiteName() { + return siteName; + } + + public void setSiteName(String siteName) { + this.siteName = siteName; + } + + public String getTenantName() { + return tenantName; + } + + public void setTenantName(String tenantName) { + this.tenantName = tenantName; + } + + public String getNetrisTag() { + return netrisTag; + } + + public void setNetrisTag(String netrisTag) { + this.netrisTag = netrisTag; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public static final class Builder { + private long zoneId; + private long hostId; + private String name; + private String url; + private String username; + private String password; + private String siteName; + private String tenantName; + private String netrisTag; + + public Builder() { + // Default constructor + } + + public Builder setZoneId(long zoneId) { + this.zoneId = zoneId; + return this; + } + + public Builder setHostId(long hostId) { + this.hostId = hostId; + return this; + } + + public Builder setName(String name) { + this.name = name; + return this; + } + + public Builder setUrl(String url) { + this.url = url; + return this; + } + + public Builder setUsername(String username) { + this.username = username; + return this; + } + + public Builder setPassword(String password) { + this.password = password; + return this; + } + + public Builder setSiteName(String siteName) { + this.siteName = siteName; + return this; + } + + public Builder setTenantName(String tenantName) { + this.tenantName = tenantName; + return this; + } + + public Builder setNetrisTag(String netrisTag) { + this.netrisTag = netrisTag; + return this; + } + + public NetrisProviderVO build() { + NetrisProviderVO provider = new NetrisProviderVO(); + provider.setZoneId(this.zoneId); + provider.setHostId(this.hostId); + provider.setUuid(UUID.randomUUID().toString()); + provider.setName(this.name); + provider.setUrl(this.url); + provider.setUsername(this.username); + provider.setPassword(this.password); + provider.setSiteName(this.siteName); + provider.setTenantName(this.tenantName); + provider.setNetrisTag(this.netrisTag); + provider.setCreated(new Date()); + return provider; + } + } +} diff --git a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java index 1dfdc5093a59..2b34e23fa4d4 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/FirewallRuleVO.java @@ -32,6 +32,8 @@ import javax.persistence.Inheritance; import javax.persistence.InheritanceType; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import javax.persistence.Transient; import com.cloud.utils.db.GenericDao; @@ -82,9 +84,16 @@ public class FirewallRuleVO implements FirewallRule { @Column(name = GenericDao.CREATED_COLUMN) Date created; + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + @Column(name = "network_id") Long networkId; + @Column(name = "vpc_id") + Long vpcId; + @Column(name = "icmp_code") Integer icmpCode; @@ -190,10 +199,18 @@ public State getState() { } @Override - public long getNetworkId() { + public Long getNetworkId() { return networkId; } + public Long getVpcId() { + return vpcId; + } + + public void setVpcId(Long vpcId) { + this.vpcId = vpcId; + } + @Override public FirewallRuleType getType() { return type; @@ -203,11 +220,15 @@ public Date getCreated() { return created; } + public Date getRemoved() { + return removed; + } + protected FirewallRuleVO() { uuid = UUID.randomUUID().toString(); } - public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, long networkId, long accountId, long domainId, + public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, Long networkId, long accountId, long domainId, Purpose purpose, List sourceCidrs, Integer icmpCode, Integer icmpType, Long related, TrafficType trafficType) { this.xId = xId; if (xId == null) { @@ -251,7 +272,7 @@ public FirewallRuleVO(String xId, long ipAddressId, int port, String protocol, l } - public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, long networkId, long accountId, long domainId, + public FirewallRuleVO(String xId, Long ipAddressId, Integer portStart, Integer portEnd, String protocol, Long networkId, long accountId, long domainId, Purpose purpose, List sourceCidrs, List destCidrs, Integer icmpCode, Integer icmpType, Long related, TrafficType trafficType) { this(xId,ipAddressId, portStart, portEnd, protocol, networkId, accountId, domainId, purpose, sourceCidrs, icmpCode, icmpType, related, trafficType); this.destinationCidrs = destCidrs; diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java index 8cd114b7fc4f..a737f1b9a205 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDao.java @@ -47,5 +47,7 @@ public interface PortForwardingRulesDao extends GenericDao listByNetworkAndDestIpAddr(String ip4Address, long networkId); + + PortForwardingRuleVO findByNetworkAndPorts(long networkId, int startPort, int endPort); int expungeByVmList(List vmIds, Long batchSize); } diff --git a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java index 1b3df06e1a2d..637f47731b47 100644 --- a/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/rules/dao/PortForwardingRulesDaoImpl.java @@ -58,6 +58,8 @@ protected PortForwardingRulesDaoImpl() { AllFieldsSearch.and("vmId", AllFieldsSearch.entity().getVirtualMachineId(), Op.EQ); AllFieldsSearch.and("purpose", AllFieldsSearch.entity().getPurpose(), Op.EQ); AllFieldsSearch.and("dstIp", AllFieldsSearch.entity().getDestinationIpAddress(), Op.EQ); + AllFieldsSearch.and("sourcePortStart", AllFieldsSearch.entity().getSourcePortStart(), Op.EQ); + AllFieldsSearch.and("sourcePortEnd", AllFieldsSearch.entity().getSourcePortEnd(), Op.EQ); AllFieldsSearch.done(); ApplicationSearch = createSearchBuilder(); @@ -175,6 +177,15 @@ public PortForwardingRuleVO findByIdAndIp(long id, String secondaryIp) { return findOneBy(sc); } + @Override + public PortForwardingRuleVO findByNetworkAndPorts(long networkId, int startPort, int endPort) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("networkId", networkId); + sc.setParameters("sourcePortStart", startPort); + sc.setParameters("sourcePortEnd", endPort); + return findOneBy(sc); + } + @Override public int expungeByVmList(List vmIds, Long batchSize) { if (CollectionUtils.isEmpty(vmIds)) { diff --git a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDao.java b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDao.java index 793334731095..2b18bcbd87bd 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDao.java +++ b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDao.java @@ -31,4 +31,6 @@ public interface SecurityGroupDao extends GenericDao { List findByAccountAndNames(Long accountId, String... names); int removeByAccountId(long accountId); + + List listByIds(List ids); } diff --git a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java index 019cf5fec462..ffb45629b625 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupDaoImpl.java @@ -129,4 +129,14 @@ public boolean expunge(Long id) { txn.commit(); return result; } + + @Override + public List listByIds(List ids) { + SearchBuilder idsSearch = createSearchBuilder(); + idsSearch.and("ids", idsSearch.entity().getId(), SearchCriteria.Op.IN); + idsSearch.done(); + SearchCriteria sc = idsSearch.create(); + sc.setParameters("ids", ids.toArray()); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupWorkDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupWorkDaoImpl.java index 327d12c759a7..3180ef30a3ce 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupWorkDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/security/dao/SecurityGroupWorkDaoImpl.java @@ -116,7 +116,7 @@ public SecurityGroupWorkVO take(long serverId) { //ensure that there is no job in Processing state for the same VM processing = true; if (logger.isTraceEnabled()) { - logger.trace("Security Group work take: found a job in Scheduled and Processing vmid=" + work.getInstanceId()); + logger.trace("Security Group work take: found a job in Scheduled and Processing vmid={}", work.getInstanceId()); } } work.setServerId(serverId); @@ -141,26 +141,16 @@ public SecurityGroupWorkVO take(long serverId) { } @Override - @DB public void updateStep(Long vmId, Long logSequenceNumber, Step step) { - final TransactionLegacy txn = TransactionLegacy.currentTxn(); - txn.start(); SearchCriteria sc = VmIdSeqNumSearch.create(); sc.setParameters("vmId", vmId); sc.setParameters("seqno", logSequenceNumber); - final Filter filter = new Filter(SecurityGroupWorkVO.class, null, true, 0l, 1l); - - final List vos = lockRows(sc, filter, true); - if (vos.size() == 0) { - txn.commit(); - return; - } - SecurityGroupWorkVO work = vos.get(0); - work.setStep(step); - update(work.getId(), work); - - txn.commit(); + SecurityGroupWorkVO workForUpdate = createForUpdate(); + workForUpdate.setStep(step); + // LIMIT 1 preserves the original single-row semantics: op_nwgrp_work has no + // uniqueness on (instance_id, seq_no), so without it duplicate rows would all be updated. + update(workForUpdate, sc, 1); } @Override @@ -172,21 +162,10 @@ public SecurityGroupWorkVO findByVmIdStep(long vmId, Step step) { } @Override - @DB public void updateStep(Long workId, Step step) { - final TransactionLegacy txn = TransactionLegacy.currentTxn(); - txn.start(); - - SecurityGroupWorkVO work = lockRow(workId, true); - if (work == null) { - txn.commit(); - return; - } - work.setStep(step); - update(work.getId(), work); - - txn.commit(); - + SecurityGroupWorkVO workForUpdate = createForUpdate(); + workForUpdate.setStep(step); + update(workId, workForUpdate); } @Override diff --git a/engine/schema/src/main/java/com/cloud/network/security/dao/VmRulesetLogDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/security/dao/VmRulesetLogDaoImpl.java index 9a9ca80bce59..7ed0ad0bcc54 100644 --- a/engine/schema/src/main/java/com/cloud/network/security/dao/VmRulesetLogDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/security/dao/VmRulesetLogDaoImpl.java @@ -76,7 +76,6 @@ public VmRulesetLogVO findByVmId(long vmId) { @Override public int createOrUpdate(Set workItems) { - //return createOrUpdateUsingBatch(workItems); return createOrUpdateUsingMultiInsert(workItems); } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java index 2246bd6eed25..632d96819cd3 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/StaticRouteVO.java @@ -27,6 +27,7 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Transient; import com.cloud.utils.db.GenericDao; @@ -42,7 +43,10 @@ public class StaticRouteVO implements StaticRoute { String uuid; @Column(name = "vpc_gateway_id", updatable = false) - long vpcGatewayId; + Long vpcGatewayId; + + @Column(name = "next_hop") + private String nextHop; @Column(name = "cidr") private String cidr; @@ -67,6 +71,9 @@ protected StaticRouteVO() { uuid = UUID.randomUUID().toString(); } + @Transient + boolean forVpn = false; + /** * @param vpcGatewayId * @param cidr @@ -74,7 +81,7 @@ protected StaticRouteVO() { * @param accountId TODO * @param domainId TODO */ - public StaticRouteVO(long vpcGatewayId, String cidr, Long vpcId, long accountId, long domainId) { + public StaticRouteVO(Long vpcGatewayId, String cidr, Long vpcId, long accountId, long domainId, String nextHop) { super(); this.vpcGatewayId = vpcGatewayId; this.cidr = cidr; @@ -82,14 +89,32 @@ public StaticRouteVO(long vpcGatewayId, String cidr, Long vpcId, long accountId, this.vpcId = vpcId; this.accountId = accountId; this.domainId = domainId; + this.nextHop = nextHop; + uuid = UUID.randomUUID().toString(); + } + + public StaticRouteVO(String cidr, Long vpcId, long accountId, long domainId, String nextHop, State state, boolean forVpn) { + super(); + this.cidr = cidr; + this.state = state; + this.vpcId = vpcId; + this.accountId = accountId; + this.domainId = domainId; + this.nextHop = nextHop; uuid = UUID.randomUUID().toString(); + this.forVpn = forVpn; } @Override - public long getVpcGatewayId() { + public Long getVpcGatewayId() { return vpcGatewayId; } + @Override + public String getNextHop() { + return nextHop; + } + @Override public String getCidr() { return cidr; @@ -145,4 +170,8 @@ public Class getEntityType() { public String getName() { return null; } + + public boolean isForVpn() { + return forVpn; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java index 274b9fedecce..b913468384e4 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcOfferingVO.java @@ -60,9 +60,6 @@ public class VpcOfferingVO implements VpcOffering { @Column(name = "default") boolean isDefault = false; - @Column(name = "for_nsx") - boolean forNsx = false; - @Column(name = "network_mode") NetworkOffering.NetworkMode networkMode; @@ -94,6 +91,9 @@ public class VpcOfferingVO implements VpcOffering { @Column(name = "specify_as_number") private Boolean specifyAsNumber = false; + @Column(name = "conserve_mode") + private boolean conserveMode; + public VpcOfferingVO() { this.uuid = UUID.randomUUID().toString(); } @@ -159,14 +159,6 @@ public boolean isDefault() { return isDefault; } - public boolean isForNsx() { - return forNsx; - } - - public void setForNsx(boolean forNsx) { - this.forNsx = forNsx; - } - public NetworkOffering.NetworkMode getNetworkMode() { return networkMode; } @@ -253,4 +245,13 @@ public Boolean isSpecifyAsNumber() { public void setSpecifyAsNumber(Boolean specifyAsNumber) { this.specifyAsNumber = specifyAsNumber; } + + @Override + public boolean isConserveMode() { + return conserveMode; + } + + public void setConserveMode(boolean conserveMode) { + this.conserveMode = conserveMode; + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java b/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java index 9fa4b505c650..ae156d7d4304 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/VpcServiceMapVO.java @@ -25,8 +25,6 @@ import javax.persistence.Id; import javax.persistence.Table; -import com.cloud.network.Network.Provider; -import com.cloud.network.Network.Service; import com.cloud.utils.db.GenericDao; @Entity @@ -72,10 +70,10 @@ public Date getCreated() { public VpcServiceMapVO() { } - public VpcServiceMapVO(long vpcId, Service service, Provider provider) { + public VpcServiceMapVO(long vpcId, String serviceName, String providerName) { this.vpcId = vpcId; - this.service = service.getName(); - this.provider = provider.getName(); + this.service = serviceName; + this.provider = providerName; } @Override 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 e8ccc2ebcf1c..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 @@ -105,6 +105,12 @@ public class VpcVO implements Vpc { @Column(name = "ip6Dns2") String ip6Dns2; + @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; @@ -309,4 +315,22 @@ public String getIp6Dns1() { public String getIp6Dns2() { return ip6Dns2; } + + @Override + public boolean useRouterIpAsResolver() { + return useRouterIpResolver; + } + + 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/network/vpc/dao/VpcDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java index 4e13fe4f5d0d..cecdce1aba42 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcDaoImpl.java @@ -142,7 +142,7 @@ public void persistVpcServiceProviders(long vpcId, Map> ser txn.start(); for (String service : serviceProviderMap.keySet()) { for (String provider : serviceProviderMap.get(service)) { - VpcServiceMapVO serviceMap = new VpcServiceMapVO(vpcId, Network.Service.getService(service), Network.Provider.getProvider(provider)); + VpcServiceMapVO serviceMap = new VpcServiceMapVO(vpcId, Network.Service.getService(service).getName(), provider); _vpcSvcMap.persist(serviceMap); } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java index 06cfd25e670b..4bc9ad646d38 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDao.java @@ -18,6 +18,7 @@ import java.util.List; +import com.cloud.network.Network; import com.cloud.network.Network.Service; import com.cloud.network.vpc.VpcOfferingServiceMapVO; import com.cloud.utils.db.GenericDao; @@ -37,4 +38,10 @@ public interface VpcOfferingServiceMapDao extends GenericDao listProvidersForServiceForVpcOffering(long vpcOfferingId, Service service); + + List listOfferingIdsByServiceAndProvider(Service service, String provider); + } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java index c7400f6edfd5..de36b6c3219b 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcOfferingServiceMapDaoImpl.java @@ -19,6 +19,7 @@ import java.util.List; +import com.cloud.network.Network; import org.springframework.stereotype.Component; import com.cloud.network.Network.Service; @@ -36,6 +37,7 @@ public class VpcOfferingServiceMapDaoImpl extends GenericDaoBase AllFieldsSearch; final SearchBuilder MultipleServicesSearch; final GenericSearchBuilder ServicesSearch; + final GenericSearchBuilder OfferingIdsByServiceAndProviderSearch; protected VpcOfferingServiceMapDaoImpl() { super(); @@ -55,6 +57,12 @@ protected VpcOfferingServiceMapDaoImpl() { ServicesSearch.and("offeringId", ServicesSearch.entity().getVpcOfferingId(), SearchCriteria.Op.EQ); ServicesSearch.select(null, Func.DISTINCT, ServicesSearch.entity().getService()); ServicesSearch.done(); + + OfferingIdsByServiceAndProviderSearch = createSearchBuilder(Long.class); + OfferingIdsByServiceAndProviderSearch.selectFields(OfferingIdsByServiceAndProviderSearch.entity().getVpcOfferingId()); + OfferingIdsByServiceAndProviderSearch.and("service", OfferingIdsByServiceAndProviderSearch.entity().getService(), SearchCriteria.Op.EQ); + OfferingIdsByServiceAndProviderSearch.and("provider", OfferingIdsByServiceAndProviderSearch.entity().getProvider(), SearchCriteria.Op.EQ); + OfferingIdsByServiceAndProviderSearch.done(); } @Override @@ -110,4 +118,30 @@ public VpcOfferingServiceMapVO findByServiceProviderAndOfferingId(String service return findOneBy(sc); } + + @Override + public boolean isProviderForVpcOffering(Network.Provider provider, long vpcOfferingId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vpcOffId", vpcOfferingId); + sc.setParameters("provider", provider.getName()); + return findOneBy(sc) != null; + } + + @Override + public List listProvidersForServiceForVpcOffering(long vpcOfferingId, Service service) { + SearchCriteria sc = AllFieldsSearch.create(); + + sc.setParameters("vpcOffId", vpcOfferingId); + sc.setParameters("service", service.getName()); + + return customSearch(sc, null); + } + + @Override + public List listOfferingIdsByServiceAndProvider(Service service, String provider) { + SearchCriteria sc = OfferingIdsByServiceAndProviderSearch.create(); + sc.setParameters("service", service.getName()); + sc.setParameters("provider", provider); + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java index 753c45fcc789..a5c4c83ff0fe 100644 --- a/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/network/vpc/dao/VpcServiceMapDaoImpl.java @@ -68,8 +68,15 @@ public boolean areServicesSupportedInVpc(long vpcId, Service... services) { @Override public boolean canProviderSupportServiceInVpc(long vpcId, Service service, Provider provider) { - // TODO Auto-generated method stub - return false; + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vpcId", vpcId); + sc.setParameters("service", service.getName()); + sc.setParameters("provider", provider.getName()); + if (findOneBy(sc) != null) { + return true; + } else { + return false; + } } @Override diff --git a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java index 5cad366945f3..904c8e646eb5 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/offerings/NetworkOfferingVO.java @@ -134,12 +134,6 @@ public class NetworkOfferingVO implements NetworkOffering { @Column(name = "for_vpc") boolean forVpc; - @Column(name = "for_tungsten") - boolean forTungsten = false; - - @Column(name = "for_nsx") - boolean forNsx = false; - @Column(name = "network_mode") NetworkMode networkMode; @@ -200,24 +194,6 @@ public void setForVpc(boolean isForVpc) { this.forVpc = isForVpc; } - @Override - public boolean isForTungsten() { - return forTungsten; - } - - public void setForTungsten(boolean forTungsten) { - this.forTungsten = forTungsten; - } - - @Override - public boolean isForNsx() { - return forNsx; - } - - public void setForNsx(boolean forNsx) { - this.forNsx = forNsx; - } - @Override public NetworkMode getNetworkMode() { return networkMode; diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDao.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDao.java index 8fb5d6dff84b..5954f95b3c57 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDao.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDao.java @@ -44,4 +44,6 @@ public interface NetworkOfferingServiceMapDao extends GenericDao listServicesForNetworkOffering(long networkOfferingId); List getDistinctProviders(long offId); + + List listOfferingIdsByServiceAndProvider(Service service, String provider); } diff --git a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDaoImpl.java index 67b341a93618..5fe86760d50a 100644 --- a/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/offerings/dao/NetworkOfferingServiceMapDaoImpl.java @@ -40,6 +40,7 @@ public class NetworkOfferingServiceMapDaoImpl extends GenericDaoBase ProvidersSearch; final GenericSearchBuilder ServicesSearch; final GenericSearchBuilder DistinctProvidersSearch; + final GenericSearchBuilder OfferingIdsByServiceAndProviderSearch; protected NetworkOfferingServiceMapDaoImpl() { super(); @@ -71,6 +72,12 @@ protected NetworkOfferingServiceMapDaoImpl() { DistinctProvidersSearch.and("provider", DistinctProvidersSearch.entity().getProvider(), SearchCriteria.Op.EQ); DistinctProvidersSearch.selectFields(DistinctProvidersSearch.entity().getProvider()); DistinctProvidersSearch.done(); + + OfferingIdsByServiceAndProviderSearch = createSearchBuilder(Long.class); + OfferingIdsByServiceAndProviderSearch.selectFields(OfferingIdsByServiceAndProviderSearch.entity().getNetworkOfferingId()); + OfferingIdsByServiceAndProviderSearch.and("service", OfferingIdsByServiceAndProviderSearch.entity().getService(), SearchCriteria.Op.EQ); + OfferingIdsByServiceAndProviderSearch.and("provider", OfferingIdsByServiceAndProviderSearch.entity().getProvider(), SearchCriteria.Op.EQ); + OfferingIdsByServiceAndProviderSearch.done(); } @Override @@ -170,4 +177,12 @@ public List getDistinctProviders(long offId) { List results = customSearch(sc, null); return results; } + + @Override + public List listOfferingIdsByServiceAndProvider(Service service, String provider) { + SearchCriteria sc = OfferingIdsByServiceAndProviderSearch.create(); + sc.setParameters("service", service.getName()); + sc.setParameters("provider", provider); + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java index 3724e03d9d06..cee5cc15e112 100644 --- a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java +++ b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDao.java @@ -22,10 +22,13 @@ import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.api.response.ResourceIconResponse; +import java.util.Collection; import java.util.List; public interface ResourceIconDao extends GenericDao { ResourceIconResponse newResourceIconResponse(ResourceIcon resourceIconVO); ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.ResourceObjectType resourceType); + List listByResourceTypeAndIds(ResourceTag.ResourceObjectType resourceType, Collection resourceIds); + List listByResourceTypeAndUuids(ResourceTag.ResourceObjectType resourceType, Collection resourceUuids); List listResourceIcons(List resourceUuids, ResourceTag.ResourceObjectType resourceType); } diff --git a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java index 1ae01bfc1ec2..49a1fe7a560a 100644 --- a/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/resource/icon/dao/ResourceIconDaoImpl.java @@ -24,8 +24,10 @@ import com.cloud.utils.db.SearchCriteria; import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.response.ResourceIconResponse; +import org.apache.commons.collections.CollectionUtils; import java.util.ArrayList; +import java.util.Collection; import java.util.List; public class ResourceIconDaoImpl extends GenericDaoBase implements ResourceIconDao { @@ -58,11 +60,36 @@ public ResourceIconVO findByResourceUuid(String resourceUuid, ResourceTag.Resour } @Override - public List listResourceIcons(List resourceUuids, ResourceTag.ResourceObjectType resourceType) { + public List listByResourceTypeAndIds(ResourceTag.ResourceObjectType resourceType, + Collection resourceIds) { + if (CollectionUtils.isEmpty(resourceIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("resourceId", sb.entity().getResourceId(), SearchCriteria.Op.IN); + sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("resourceId", resourceIds.toArray()); + sc.setParameters("resourceType", resourceType); + return listBy(sc); + } + + @Override + public List listByResourceTypeAndUuids(ResourceTag.ResourceObjectType resourceType, + Collection resourceUuids) { + if (CollectionUtils.isEmpty(resourceUuids)) { + return new ArrayList<>(); + } SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("uuid", resourceUuids.toArray()); sc.setParameters("resourceType", resourceType); - List resourceIcons = listBy(sc); + return listBy(sc); + } + + @Override + public List listResourceIcons(List resourceUuids, ResourceTag.ResourceObjectType resourceType) { + List resourceIcons = listByResourceTypeAndUuids(resourceType, resourceUuids); List iconResponses = new ArrayList<>(); for (ResourceIconVO resourceIcon : resourceIcons) { ResourceIconResponse response = new ResourceIconResponse(); diff --git a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java index 7f5c1a7afa19..cfe8049f5b2c 100644 --- a/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/service/ServiceOfferingVO.java @@ -124,6 +124,15 @@ public class ServiceOfferingVO implements ServiceOffering { @Column(name = "dynamic_scaling_enabled") private boolean dynamicScalingEnabled = true; + @Column(name = "vgpu_profile_id") + private Long vgpuProfileId; + + @Column(name = "gpu_count") + private Integer gpuCount; + + @Column(name = "gpu_display") + private Boolean gpuDisplay; + // This is a delayed load value. If the value is null, // then this field has not been loaded yet. // Call service offering dao to load it. @@ -198,6 +207,8 @@ public ServiceOfferingVO(ServiceOfferingVO offering) { systemUse = offering.isSystemUse(); dynamicScalingEnabled = offering.isDynamicScalingEnabled(); diskOfferingStrictness = offering.diskOfferingStrictness; + vgpuProfileId = offering.vgpuProfileId; + gpuCount = offering.gpuCount; } @Override @@ -445,4 +456,30 @@ public Boolean getDiskOfferingStrictness() { public void setDiskOfferingStrictness(boolean diskOfferingStrictness) { this.diskOfferingStrictness = diskOfferingStrictness; } + + @Override + public Long getVgpuProfileId() { + return vgpuProfileId; + } + + public void setVgpuProfileId(Long vgpuProfileId) { + this.vgpuProfileId = vgpuProfileId; + } + + @Override + public Integer getGpuCount() { + return gpuCount; + } + + public void setGpuCount(Integer gpuCount) { + this.gpuCount = gpuCount; + } + + public Boolean getGpuDisplay() { + return gpuDisplay; + } + + public void setGpuDisplay(Boolean gpuDisplay) { + this.gpuDisplay = gpuDisplay; + } } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java index ceb5b0a4fc1c..d3bab4fcbe27 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDao.java @@ -22,6 +22,7 @@ import com.cloud.service.ServiceOfferingVO; import com.cloud.storage.Storage.ProvisioningType; import com.cloud.utils.db.GenericDao; +import com.cloud.utils.db.SearchBuilder; import com.cloud.vm.VirtualMachine; /* @@ -57,4 +58,6 @@ List createSystemServiceOfferings(String name, String uniqueN ServiceOfferingVO findServiceOfferingByComputeOnlyDiskOffering(long diskOfferingId, boolean includingRemoved); List listIdsByHostTag(String tag); + + void addCheckForGpuEnabled(SearchBuilder serviceOfferingSearch, Boolean gpuEnabled); } diff --git a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java index 803522fa6aa6..f360770ad686 100644 --- a/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/service/dao/ServiceOfferingDaoImpl.java @@ -39,7 +39,7 @@ import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.VirtualMachine; -import com.cloud.vm.dao.UserVmDetailsDao; +import com.cloud.vm.dao.VMInstanceDetailsDao; @Component @DB() @@ -48,7 +48,7 @@ public class ServiceOfferingDaoImpl extends GenericDaoBase dynamicOffering = userVmDetailsDao.listDetailsKeyPairs(vmId); + Map dynamicOffering = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); return getComputeOffering(offering, dynamicOffering); } return offering; @@ -190,7 +190,7 @@ public ServiceOfferingVO findByIdIncludingRemoved(Long vmId, long serviceOfferin if (vmId == null) { throw new CloudRuntimeException("missing argument vmId"); } - Map dynamicOffering = userVmDetailsDao.listDetailsKeyPairs(vmId); + Map dynamicOffering = vmInstanceDetailsDao.listDetailsKeyPairs(vmId); return getComputeOffering(offering, dynamicOffering); } return offering; @@ -312,4 +312,13 @@ public List listIdsByHostTag(String tag) { sc.setParameters("tagEndLike", "%," + tag); return customSearch(sc, null); } + + @Override + public void addCheckForGpuEnabled(SearchBuilder serviceOfferingSearch, Boolean gpuEnabled) { + if (gpuEnabled) { + serviceOfferingSearch.and("gpuEnabled", serviceOfferingSearch.entity().getVgpuProfileId(), SearchCriteria.Op.NNULL); + } else { + serviceOfferingSearch.and("gpuDisabled", serviceOfferingSearch.entity().getVgpuProfileId(), SearchCriteria.Op.NULL); + } + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java index 79f5bcb51578..7f6b6d8adf0e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/DiskOfferingVO.java @@ -577,11 +577,11 @@ public Integer getHypervisorSnapshotReserve() { @Override public void setEncrypt(boolean encrypt) { this.encrypt = encrypt; } + @Override public boolean isShared() { return !useLocalStorage; } - public boolean getDiskSizeStrictness() { return diskSizeStrictness; } diff --git a/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java b/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java index 36773e351e36..642705ffcbe4 100644 --- a/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/GuestOSCategoryVO.java @@ -16,6 +16,7 @@ // under the License. package com.cloud.storage; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; @@ -25,6 +26,8 @@ import javax.persistence.Id; import javax.persistence.Table; +import com.cloud.utils.db.GenericDao; + @Entity @Table(name = "guest_os_category") public class GuestOSCategoryVO implements GuestOsCategory { @@ -39,6 +42,26 @@ public class GuestOSCategoryVO implements GuestOsCategory { @Column(name = "uuid") String uuid = UUID.randomUUID().toString(); + @Column(name = "featured") + boolean featured; + + @Column(name = "sort_key") + private int sortKey; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + private Date removed; + + public GuestOSCategoryVO() { + } + + public GuestOSCategoryVO(String name, boolean featured) { + this.name = name; + this.featured = featured; + } + @Override public long getId() { return id; @@ -59,7 +82,25 @@ public String getUuid() { return this.uuid; } - public void setUuid(String uuid) { - this.uuid = uuid; + @Override + public boolean isFeatured() { + return featured; + } + + public void setFeatured(Boolean featured) { + this.featured = featured; + } + + public void setSortKey(int key) { + sortKey = key; + } + + public int getSortKey() { + return sortKey; + } + + @Override + public Date getCreated() { + return created; } } diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java index f57d9d3dccf2..299c6380ab63 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotPolicyVO.java @@ -59,6 +59,12 @@ public class SnapshotPolicyVO implements SnapshotPolicy { @Column(name = "uuid") String uuid; + @Column(name = "account_id") + long accountId; + + @Column(name = "domain_id") + long domainId; + @Column(name = "display", updatable = true, nullable = false) protected boolean display = true; @@ -66,7 +72,7 @@ public SnapshotPolicyVO() { this.uuid = UUID.randomUUID().toString(); } - public SnapshotPolicyVO(long volumeId, String schedule, String timezone, IntervalType intvType, int maxSnaps, boolean display) { + public SnapshotPolicyVO(long volumeId, String schedule, String timezone, IntervalType intvType, int maxSnaps, long accountId, long domainId, boolean display) { this.volumeId = volumeId; this.schedule = schedule; this.timezone = timezone; @@ -75,6 +81,8 @@ public SnapshotPolicyVO(long volumeId, String schedule, String timezone, Interva this.active = true; this.display = display; this.uuid = UUID.randomUUID().toString(); + this.accountId = accountId; + this.domainId = domainId; } @Override @@ -160,4 +168,32 @@ public boolean isDisplay() { public void setDisplay(boolean display) { this.display = display; } + + @Override + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + @Override + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + @Override + public Class getEntityType() { + return SnapshotPolicy.class; + } + + @Override + public String getName() { + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java b/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java index 19c67a91e2c8..be7ba2843321 100644 --- a/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/SnapshotVO.java @@ -248,6 +248,10 @@ public Date getRemoved() { return removed; } + public void setRemoved(Date removed) { + this.removed = removed; + } + @Override public State getState() { return state; @@ -284,6 +288,6 @@ public Class getEntityType() { public String toString() { return String.format("Snapshot %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( - this, "id", "uuid", "name", "volumeId", "version")); + this, "id", "uuid", "name", "volumeId", "version", "state")); } } diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java new file mode 100644 index 000000000000..5690324340c4 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolAndAccessGroupMapVO.java @@ -0,0 +1,64 @@ +// 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.storage; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "storage_pool_and_access_group_map") +public class StoragePoolAndAccessGroupMapVO implements InternalIdentity { + + protected StoragePoolAndAccessGroupMapVO() { + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "pool_id") + private long poolId; + + @Column(name = "storage_access_group") + private String storageAccessGroup; + + public StoragePoolAndAccessGroupMapVO(long poolId, String storageAccessGroup) { + this.poolId = poolId; + this.storageAccessGroup = storageAccessGroup; + } + + @Override + public long getId() { + return this.id; + } + + public long getPoolId() { + return poolId; + } + + public String getStorageAccessGroup() { + return storageAccessGroup; + } + +} diff --git a/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java b/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java index 53c08322e0cf..73a11b02c050 100644 --- a/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/StoragePoolHostVO.java @@ -28,6 +28,7 @@ import javax.persistence.TemporalType; import com.cloud.utils.db.GenericDaoBase; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; /** * Join table for storage pools and hosts @@ -100,4 +101,9 @@ public void setLocalPath(String localPath) { this.localPath = localPath; } + @Override + public String toString() { + return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "hostId", "poolId"); + } + } diff --git a/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java b/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java index 10d08601515b..88d3b7ba2d8d 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VMTemplateVO.java @@ -34,6 +34,7 @@ import com.cloud.cpu.CPU; import com.cloud.user.UserData; +import com.cloud.utils.UuidUtils; import org.apache.cloudstack.util.CPUArchConverter; import org.apache.cloudstack.util.HypervisorTypeConverter; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @@ -162,6 +163,9 @@ public class VMTemplateVO implements VirtualMachineTemplate { @Column(name = "deploy_as_is") private boolean deployAsIs; + @Column(name = "for_cks") + private boolean forCks; + @Column(name = "user_data_id") private Long userDataId; @@ -173,6 +177,9 @@ public class VMTemplateVO implements VirtualMachineTemplate { @Convert(converter = CPUArchConverter.class) private CPU.CPUArch arch; + @Column(name = "extension_id") + private Long extensionId; + @Override public String getUniqueName() { return uniqueName; @@ -215,7 +222,7 @@ private VMTemplateVO(long id, String name, ImageFormat format, boolean isPublic, public VMTemplateVO(long id, String name, ImageFormat format, boolean isPublic, boolean featured, boolean isExtractable, TemplateType type, String url, boolean requiresHvm, int bits, long accountId, String cksum, String displayText, boolean enablePassword, long guestOSId, boolean bootable, HypervisorType hyperType, String templateTag, Map details, boolean sshKeyEnabled, boolean isDynamicallyScalable, boolean directDownload, - boolean deployAsIs, CPU.CPUArch arch) { + boolean deployAsIs, CPU.CPUArch arch, Long extensionId) { this(id, name, format, @@ -242,6 +249,7 @@ public VMTemplateVO(long id, String name, ImageFormat format, boolean isPublic, this.directDownload = directDownload; this.deployAsIs = deployAsIs; this.arch = arch; + this.extensionId = extensionId; } public static VMTemplateVO createPreHostIso(Long id, String uniqueName, String name, ImageFormat format, boolean isPublic, boolean featured, TemplateType type, @@ -337,7 +345,7 @@ private static String generateUniqueName(long id, long userId, String displayNam name.append("-"); name.append(userId); name.append("-"); - name.append(UUID.nameUUIDFromBytes((displayName + System.currentTimeMillis()).getBytes()).toString()); + name.append(UuidUtils.nameUUIDFromBytes((displayName + System.currentTimeMillis()).getBytes()).toString()); return name.toString(); } @@ -664,6 +672,14 @@ public void setDeployAsIs(boolean deployAsIs) { this.deployAsIs = deployAsIs; } + public boolean isForCks() { + return forCks; + } + + public void setForCks(boolean forCks) { + this.forCks = forCks; + } + @Override public Long getUserDataId() { return userDataId; @@ -691,4 +707,11 @@ public void setArch(CPU.CPUArch arch) { this.arch = arch; } + public Long getExtensionId() { + return extensionId; + } + + public void setExtensionId(Long extensionId) { + this.extensionId = extensionId; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java b/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java index 653be54a9109..3815125b3487 100644 --- a/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java +++ b/engine/schema/src/main/java/com/cloud/storage/VolumeVO.java @@ -182,6 +182,12 @@ public class VolumeVO implements Volume { @Column(name = "passphrase_id") private Long passphraseId; + @Column(name = "kms_key_id") + private Long kmsKeyId; + + @Column(name = "kms_wrapped_key_id") + private Long kmsWrappedKeyId; + @Column(name = "encrypt_format") private String encryptFormat; @@ -683,6 +689,14 @@ public void setExternalUuid(String externalUuid) { public void setPassphraseId(Long id) { this.passphraseId = id; } + public Long getKmsKeyId() { return kmsKeyId; } + + public void setKmsKeyId(Long id) { this.kmsKeyId = id; } + + public Long getKmsWrappedKeyId() { return kmsWrappedKeyId; } + + public void setKmsWrappedKeyId(Long id) { this.kmsWrappedKeyId = id; } + public String getEncryptFormat() { return encryptFormat; } public void setEncryptFormat(String encryptFormat) { this.encryptFormat = encryptFormat; } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDao.java index f45f28b5c2c2..2511df49807e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDao.java @@ -27,4 +27,8 @@ public interface BucketDao extends GenericDao { List listByObjectStoreIdAndAccountId(long objectStoreId, long accountId); List searchByIds(Long[] ids); + + Long countBucketsForAccount(long accountId); + + Long calculateObjectStorageAllocationForAccount(long accountId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java index 98bef6201a15..473879d933dc 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/BucketDaoImpl.java @@ -16,8 +16,10 @@ // under the License. package com.cloud.storage.dao; +import com.cloud.configuration.Resource; import com.cloud.storage.BucketVO; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import org.springframework.stereotype.Component; @@ -31,6 +33,8 @@ public class BucketDaoImpl extends GenericDaoBase implements Buc private SearchBuilder searchFilteringStoreId; private SearchBuilder bucketSearch; + private GenericSearchBuilder CountBucketsByAccount; + private GenericSearchBuilder CalculateBucketsQuotaByAccount; private static final String STORE_ID = "store_id"; private static final String STATE = "state"; @@ -54,6 +58,20 @@ public boolean configure(String name, Map params) throws Configu bucketSearch.and("idIN", bucketSearch.entity().getId(), SearchCriteria.Op.IN); bucketSearch.done(); + CountBucketsByAccount = createSearchBuilder(Long.class); + CountBucketsByAccount.select(null, SearchCriteria.Func.COUNT, null); + CountBucketsByAccount.and(ACCOUNT_ID, CountBucketsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + CountBucketsByAccount.and(STATE, CountBucketsByAccount.entity().getState(), SearchCriteria.Op.NIN); + CountBucketsByAccount.and("removed", CountBucketsByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); + CountBucketsByAccount.done(); + + CalculateBucketsQuotaByAccount = createSearchBuilder(SumCount.class); + CalculateBucketsQuotaByAccount.select("sum", SearchCriteria.Func.SUM, CalculateBucketsQuotaByAccount.entity().getQuota()); + CalculateBucketsQuotaByAccount.and(ACCOUNT_ID, CalculateBucketsQuotaByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + CalculateBucketsQuotaByAccount.and(STATE, CalculateBucketsQuotaByAccount.entity().getState(), SearchCriteria.Op.NIN); + CalculateBucketsQuotaByAccount.and("removed", CalculateBucketsQuotaByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); + CalculateBucketsQuotaByAccount.done(); + return true; } @Override @@ -79,4 +97,21 @@ public List searchByIds(Long[] ids) { sc.setParameters("idIN", ids); return search(sc, null, null, false); } + + @Override + public Long countBucketsForAccount(long accountId) { + SearchCriteria sc = CountBucketsByAccount.create(); + sc.setParameters(ACCOUNT_ID, accountId); + sc.setParameters(STATE, BucketVO.State.Destroyed); + return customSearch(sc, null).get(0); + } + + @Override + public Long calculateObjectStorageAllocationForAccount(long accountId) { + SearchCriteria sc = CalculateBucketsQuotaByAccount.create(); + sc.setParameters(ACCOUNT_ID, accountId); + sc.setParameters(STATE, BucketVO.State.Destroyed); + Long totalQuota = customSearch(sc, null).get(0).sum; + return (totalQuota * Resource.ResourceType.bytesToGiB); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java index f726bca3c5d6..ea97f663768f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDao.java @@ -30,7 +30,9 @@ public interface DiskOfferingDao extends GenericDao { List listAllBySizeAndProvisioningType(long size, Storage.ProvisioningType provisioningType); - List findCustomDiskOfferings(); + List listCustomDiskOfferings(); + List listByStorageTag(String tag); + List listAllActiveAndNonComputeDiskOfferings(); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java index 93e747662775..44e6551da17d 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/DiskOfferingDaoImpl.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.storage.dao; +import static org.apache.cloudstack.query.QueryService.SortKeyAscending; + import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; @@ -26,12 +28,14 @@ import javax.inject.Inject; import javax.persistence.EntityExistsException; +import com.cloud.offering.DiskOffering; import org.apache.cloudstack.resourcedetail.dao.DiskOfferingDetailsDao; import org.springframework.stereotype.Component; import com.cloud.storage.DiskOfferingVO; import com.cloud.storage.Storage; import com.cloud.utils.db.Attribute; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -45,6 +49,8 @@ public class DiskOfferingDaoImpl extends GenericDaoBase im protected DiskOfferingDetailsDao detailsDao; protected final SearchBuilder UniqueNameSearch; + protected final SearchBuilder ActiveAndNonComputeSearch; + private final String SizeDiskOfferingSearch = "SELECT * FROM disk_offering WHERE " + "disk_size = ? AND provisioning_type = ? AND removed IS NULL"; @@ -56,6 +62,11 @@ protected DiskOfferingDaoImpl() { UniqueNameSearch.and("name", UniqueNameSearch.entity().getUniqueName(), SearchCriteria.Op.EQ); UniqueNameSearch.done(); + ActiveAndNonComputeSearch = createSearchBuilder(); + ActiveAndNonComputeSearch.and("state", ActiveAndNonComputeSearch.entity().getState(), SearchCriteria.Op.EQ); + ActiveAndNonComputeSearch.and("computeOnly", ActiveAndNonComputeSearch.entity().isComputeOnly(), SearchCriteria.Op.EQ); + ActiveAndNonComputeSearch.done(); + _computeOnlyAttr = _allAttributes.get("computeOnly"); } @@ -130,13 +141,14 @@ public List listAllBySizeAndProvisioningType(long size, Storage. } @Override - public List findCustomDiskOfferings() { + public List listCustomDiskOfferings() { SearchBuilder sb = createSearchBuilder(); sb.and("customized", sb.entity().isCustomized(), SearchCriteria.Op.EQ); sb.done(); SearchCriteria sc = sb.create(); sc.setParameters("customized", true); - return listBy(sc); + Filter searchFilter = new Filter(DiskOfferingVO.class, "sortKey", SortKeyAscending.value()); + return listBy(sc, searchFilter); } @Override @@ -164,4 +176,12 @@ public List listByStorageTag(String tag) { sc.setParameters("tagEndLike", "%," + tag); return listBy(sc); } + + @Override + public List listAllActiveAndNonComputeDiskOfferings() { + SearchCriteria sc = ActiveAndNonComputeSearch.create(); + sc.setParameters("state", DiskOffering.State.Active); + sc.setParameters("computeOnly", false); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java index 6fad6c5c47ee..cc1c96aee52c 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSCategoryDaoImpl.java @@ -27,7 +27,6 @@ public class GuestOSCategoryDaoImpl extends GenericDaoBase implements GuestOSCategoryDao { protected GuestOSCategoryDaoImpl() { - } @Override diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java index 13cd398073ad..f24f3f3b67c4 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDao.java @@ -16,14 +16,14 @@ // under the License. package com.cloud.storage.dao; +import java.util.List; +import java.util.Set; + import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSVO; import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; -import java.util.List; -import java.util.Set; - public interface GuestOSDao extends GenericDao { GuestOSVO findOneByDisplayName(String displayName); @@ -35,5 +35,7 @@ public interface GuestOSDao extends GenericDao { List listByDisplayName(String displayName); - Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay); + Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, List ids, Long osCategoryId, String description, String keyword, Boolean forDisplay); + + List listIdsByCategoryId(final long categoryId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java index efcaa482a676..09f8772fb59c 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/GuestOSDaoImpl.java @@ -25,19 +25,20 @@ import java.util.List; import java.util.Set; -import com.cloud.storage.GuestOS; -import com.cloud.utils.Pair; -import com.cloud.utils.db.DB; -import com.cloud.utils.db.TransactionLegacy; -import com.cloud.utils.exception.CloudRuntimeException; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; +import com.cloud.storage.GuestOS; import com.cloud.storage.GuestOSVO; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; @Component public class GuestOSDaoImpl extends GenericDaoBase implements GuestOSDao { @@ -124,12 +125,12 @@ public List listByDisplayName(String displayName) { return listBy(sc); } - public Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, Long id, Long osCategoryId, String description, String keyword, Boolean forDisplay) { + public Pair, Integer> listGuestOSByCriteria(Long startIndex, Long pageSize, List ids, Long osCategoryId, String description, String keyword, Boolean forDisplay) { final Filter searchFilter = new Filter(GuestOSVO.class, "displayName", true, startIndex, pageSize); final SearchCriteria sc = createSearchCriteria(); - if (id != null) { - sc.addAnd("id", SearchCriteria.Op.EQ, id); + if (CollectionUtils.isNotEmpty(ids)) { + sc.addAnd("id", SearchCriteria.Op.IN, ids.toArray()); } if (osCategoryId != null) { @@ -152,4 +153,14 @@ public Pair, Integer> listGuestOSByCriteria(Long startIn return new Pair<>(result.first(), result.second()); } + @Override + public List listIdsByCategoryId(final long categoryId) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.selectFields(sb.entity().getId()); + sb.and("categoryId", sb.entity().getCategoryId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("categoryId", categoryId); + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java index 737e4a5a53ca..f851f158331e 100755 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDao.java @@ -60,4 +60,6 @@ public interface SnapshotDao extends GenericDao, StateDao listByIds(Object... ids); List searchByVolumes(List volumeIds); + + List listByVolumeIdAndTypeNotInAndStateNotRemoved(long volumeId, Type... type); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java index 238ae54e07f3..a5596ed840cc 100755 --- a/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/SnapshotDaoImpl.java @@ -19,6 +19,7 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import javax.annotation.PostConstruct; @@ -55,6 +56,18 @@ public class SnapshotDaoImpl extends GenericDaoBase implements // TODO: we should remove these direct sqls private static final String GET_LAST_SNAPSHOT = "SELECT snapshots.id FROM snapshot_store_ref, snapshots where snapshots.id = snapshot_store_ref.snapshot_id AND snapshosts.volume_id = ? AND snapshot_store_ref.role = ? ORDER BY created DESC"; + private static final String VOLUME_ID = "volumeId"; + private static final String TYPE = "type"; + private static final String STATUS = "status"; + private static final String VERSION = "version"; + private static final String ACCOUNT_ID = "accountId"; + private static final String REMOVED = "removed"; + private static final String NOT_TYPE = "notType"; + private static final String ID = "id"; + private static final String INSTANCE_ID = "instanceId"; + private static final String STATE = "state"; + private static final String INSTANCE_VOLUMES = "instanceVolumes"; + private static final String INSTANCE_SNAPSHOTS = "instanceSnapshots"; private SearchBuilder snapshotIdsSearch; private SearchBuilder VolumeIdSearch; @@ -66,6 +79,8 @@ public class SnapshotDaoImpl extends GenericDaoBase implements private SearchBuilder StatusSearch; private SearchBuilder notInStatusSearch; private GenericSearchBuilder CountSnapshotsByAccount; + + private SearchBuilder volumeIdAndTypeNotInSearch; @Inject ResourceTagDao _tagsDao; @Inject @@ -76,9 +91,9 @@ public class SnapshotDaoImpl extends GenericDaoBase implements @Override public List listByVolumeIdTypeNotDestroyed(long volumeId, Type type) { SearchCriteria sc = VolumeIdTypeNotDestroyedSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("type", type.ordinal()); - sc.setParameters("status", State.Destroyed); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(TYPE, type.ordinal()); + sc.setParameters(STATUS, State.Destroyed); return listBy(sc, null); } @@ -95,28 +110,28 @@ public List listByVolumeId(long volumeId) { @Override public List listByVolumeId(Filter filter, long volumeId) { SearchCriteria sc = VolumeIdSearch.create(); - sc.setParameters("volumeId", volumeId); + sc.setParameters(VOLUME_ID, volumeId); return listBy(sc, filter); } @Override public List listByVolumeIdIncludingRemoved(long volumeId) { SearchCriteria sc = VolumeIdSearch.create(); - sc.setParameters("volumeId", volumeId); + sc.setParameters(VOLUME_ID, volumeId); return listIncludingRemovedBy(sc, null); } public List listByVolumeIdType(Filter filter, long volumeId, Type type) { SearchCriteria sc = VolumeIdTypeSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("type", type.ordinal()); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(TYPE, type.ordinal()); return listBy(sc, filter); } public List listByVolumeIdVersion(Filter filter, long volumeId, String version) { SearchCriteria sc = VolumeIdVersionSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("version", version); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(VERSION, version); return listBy(sc, filter); } @@ -126,61 +141,68 @@ public SnapshotDaoImpl() { @PostConstruct protected void init() { VolumeIdSearch = createSearchBuilder(); - VolumeIdSearch.and("volumeId", VolumeIdSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + VolumeIdSearch.and(VOLUME_ID, VolumeIdSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); VolumeIdSearch.done(); VolumeIdTypeSearch = createSearchBuilder(); - VolumeIdTypeSearch.and("volumeId", VolumeIdTypeSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); - VolumeIdTypeSearch.and("type", VolumeIdTypeSearch.entity().getSnapshotType(), SearchCriteria.Op.EQ); + VolumeIdTypeSearch.and(VOLUME_ID, VolumeIdTypeSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + VolumeIdTypeSearch.and(TYPE, VolumeIdTypeSearch.entity().getSnapshotType(), SearchCriteria.Op.EQ); VolumeIdTypeSearch.done(); VolumeIdTypeNotDestroyedSearch = createSearchBuilder(); - VolumeIdTypeNotDestroyedSearch.and("volumeId", VolumeIdTypeNotDestroyedSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); - VolumeIdTypeNotDestroyedSearch.and("type", VolumeIdTypeNotDestroyedSearch.entity().getSnapshotType(), SearchCriteria.Op.EQ); - VolumeIdTypeNotDestroyedSearch.and("status", VolumeIdTypeNotDestroyedSearch.entity().getState(), SearchCriteria.Op.NEQ); + VolumeIdTypeNotDestroyedSearch.and(VOLUME_ID, VolumeIdTypeNotDestroyedSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + VolumeIdTypeNotDestroyedSearch.and(TYPE, VolumeIdTypeNotDestroyedSearch.entity().getSnapshotType(), SearchCriteria.Op.EQ); + VolumeIdTypeNotDestroyedSearch.and(STATUS, VolumeIdTypeNotDestroyedSearch.entity().getState(), SearchCriteria.Op.NEQ); VolumeIdTypeNotDestroyedSearch.done(); VolumeIdVersionSearch = createSearchBuilder(); - VolumeIdVersionSearch.and("volumeId", VolumeIdVersionSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); - VolumeIdVersionSearch.and("version", VolumeIdVersionSearch.entity().getVersion(), SearchCriteria.Op.EQ); + VolumeIdVersionSearch.and(VOLUME_ID, VolumeIdVersionSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + VolumeIdVersionSearch.and(VERSION, VolumeIdVersionSearch.entity().getVersion(), SearchCriteria.Op.EQ); VolumeIdVersionSearch.done(); AccountIdSearch = createSearchBuilder(); - AccountIdSearch.and("accountId", AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AccountIdSearch.and(ACCOUNT_ID, AccountIdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); AccountIdSearch.done(); StatusSearch = createSearchBuilder(); - StatusSearch.and("volumeId", StatusSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); - StatusSearch.and("status", StatusSearch.entity().getState(), SearchCriteria.Op.IN); + StatusSearch.and(VOLUME_ID, StatusSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + StatusSearch.and(STATUS, StatusSearch.entity().getState(), SearchCriteria.Op.IN); StatusSearch.done(); notInStatusSearch = createSearchBuilder(); - notInStatusSearch.and("volumeId", notInStatusSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); - notInStatusSearch.and("status", notInStatusSearch.entity().getState(), SearchCriteria.Op.NOTIN); + notInStatusSearch.and(VOLUME_ID, notInStatusSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + notInStatusSearch.and(STATUS, notInStatusSearch.entity().getState(), SearchCriteria.Op.NOTIN); notInStatusSearch.done(); CountSnapshotsByAccount = createSearchBuilder(Long.class); CountSnapshotsByAccount.select(null, Func.COUNT, null); - CountSnapshotsByAccount.and("account", CountSnapshotsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); - CountSnapshotsByAccount.and("status", CountSnapshotsByAccount.entity().getState(), SearchCriteria.Op.NIN); - CountSnapshotsByAccount.and("removed", CountSnapshotsByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); + CountSnapshotsByAccount.and(ACCOUNT_ID, CountSnapshotsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + CountSnapshotsByAccount.and(STATUS, CountSnapshotsByAccount.entity().getState(), SearchCriteria.Op.NIN); + CountSnapshotsByAccount.and("snapshotTypeNEQ", CountSnapshotsByAccount.entity().getSnapshotType(), SearchCriteria.Op.NIN); + CountSnapshotsByAccount.and(REMOVED, CountSnapshotsByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); CountSnapshotsByAccount.done(); InstanceIdSearch = createSearchBuilder(); - InstanceIdSearch.and("status", InstanceIdSearch.entity().getState(), SearchCriteria.Op.IN); + InstanceIdSearch.and(STATUS, InstanceIdSearch.entity().getState(), SearchCriteria.Op.IN); snapshotIdsSearch = createSearchBuilder(); - snapshotIdsSearch.and("id", snapshotIdsSearch.entity().getId(), SearchCriteria.Op.IN); + snapshotIdsSearch.and(ID, snapshotIdsSearch.entity().getId(), SearchCriteria.Op.IN); SearchBuilder instanceSearch = _instanceDao.createSearchBuilder(); - instanceSearch.and("instanceId", instanceSearch.entity().getId(), SearchCriteria.Op.EQ); + instanceSearch.and(INSTANCE_ID, instanceSearch.entity().getId(), SearchCriteria.Op.EQ); SearchBuilder volumeSearch = _volumeDao.createSearchBuilder(); - volumeSearch.and("state", volumeSearch.entity().getState(), SearchCriteria.Op.EQ); - volumeSearch.join("instanceVolumes", instanceSearch, instanceSearch.entity().getId(), volumeSearch.entity().getInstanceId(), JoinType.INNER); + volumeSearch.and(STATE, volumeSearch.entity().getState(), SearchCriteria.Op.EQ); + volumeSearch.join(INSTANCE_VOLUMES, instanceSearch, instanceSearch.entity().getId(), volumeSearch.entity().getInstanceId(), JoinType.INNER); - InstanceIdSearch.join("instanceSnapshots", volumeSearch, volumeSearch.entity().getId(), InstanceIdSearch.entity().getVolumeId(), JoinType.INNER); + InstanceIdSearch.join(INSTANCE_SNAPSHOTS, volumeSearch, volumeSearch.entity().getId(), InstanceIdSearch.entity().getVolumeId(), JoinType.INNER); InstanceIdSearch.done(); + + volumeIdAndTypeNotInSearch = createSearchBuilder(); + volumeIdAndTypeNotInSearch.and(VOLUME_ID, volumeIdAndTypeNotInSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + volumeIdAndTypeNotInSearch.and(STATUS, volumeIdAndTypeNotInSearch.entity().getState(), SearchCriteria.Op.NEQ); + volumeIdAndTypeNotInSearch.and(NOT_TYPE, volumeIdAndTypeNotInSearch.entity().getTypeDescription(), SearchCriteria.Op.NOTIN); + volumeIdAndTypeNotInSearch.done(); } @Override @@ -205,8 +227,9 @@ public long getLastSnapshot(long volumeId, DataStoreRole role) { @Override public Long countSnapshotsForAccount(long accountId) { SearchCriteria sc = CountSnapshotsByAccount.create(); - sc.setParameters("account", accountId); - sc.setParameters("status", State.Error, State.Destroyed); + sc.setParameters(ACCOUNT_ID, accountId); + sc.setParameters(STATUS, State.Error, State.Destroyed); + sc.setParameters("snapshotTypeNEQ", Snapshot.Type.GROUP.ordinal()); return customSearch(sc, null).get(0); } @@ -215,19 +238,19 @@ public List listByInstanceId(long instanceId, Snapshot.State... stat SearchCriteria sc = InstanceIdSearch.create(); if (status != null && status.length != 0) { - sc.setParameters("status", (Object[])status); + sc.setParameters(STATUS, (Object[])status); } - sc.setJoinParameters("instanceSnapshots", "state", Volume.State.Ready); - sc.setJoinParameters("instanceVolumes", "instanceId", instanceId); + sc.setJoinParameters(INSTANCE_SNAPSHOTS, STATE, Volume.State.Ready); + sc.setJoinParameters(INSTANCE_VOLUMES, INSTANCE_ID, instanceId); return listBy(sc, null); } @Override public List listByStatus(long volumeId, Snapshot.State... status) { SearchCriteria sc = StatusSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("status", (Object[])status); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(STATUS, (Object[])status); return listBy(sc, null); } @@ -248,7 +271,7 @@ public boolean remove(Long id) { @Override public List listAllByStatus(Snapshot.State... status) { SearchCriteria sc = StatusSearch.create(); - sc.setParameters("status", (Object[])status); + sc.setParameters(STATUS, (Object[])status); return listBy(sc, null); } @@ -262,7 +285,7 @@ public List listAllByStatusIncludingRemoved(Snapshot.State... status @Override public List listByIds(Object... ids) { SearchCriteria sc = snapshotIdsSearch.create(); - sc.setParameters("id", ids); + sc.setParameters(ID, ids); return listBy(sc, null); } @@ -280,7 +303,7 @@ public boolean updateState(State currentState, Event event, State nextState, Sna @Override public void updateVolumeIds(long oldVolId, long newVolId) { SearchCriteria sc = VolumeIdSearch.create(); - sc.setParameters("volumeId", oldVolId); + sc.setParameters(VOLUME_ID, oldVolId); SnapshotVO snapshot = createForUpdate(); snapshot.setVolumeId(newVolId); UpdateBuilder ub = getUpdateBuilder(snapshot); @@ -290,8 +313,8 @@ public void updateVolumeIds(long oldVolId, long newVolId) { @Override public List listByStatusNotIn(long volumeId, Snapshot.State... status) { SearchCriteria sc = this.notInStatusSearch.create(); - sc.setParameters("volumeId", volumeId); - sc.setParameters("status", (Object[]) status); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(STATUS, (Object[]) status); return listBy(sc, null); } @@ -306,4 +329,14 @@ public List searchByVolumes(List volumeIds) { sc.setParameters("volumeIds", volumeIds.toArray()); return search(sc, null); } + + @Override + public List listByVolumeIdAndTypeNotInAndStateNotRemoved(long volumeId, Type... types) { + SearchCriteria sc = volumeIdAndTypeNotInSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(NOT_TYPE, Arrays.stream(types).map(Type::toString).toArray()); + sc.setParameters(STATUS, State.Destroyed); + + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolAndAccessGroupMapDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolAndAccessGroupMapDao.java new file mode 100644 index 000000000000..3ff797f7e741 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolAndAccessGroupMapDao.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.storage.dao; + +import java.util.List; + +import com.cloud.storage.StoragePoolAndAccessGroupMapVO; + +import com.cloud.utils.db.GenericDao; + +public interface StoragePoolAndAccessGroupMapDao extends GenericDao { + + void persist(long poolId, List storageAccessGroups); + List getStorageAccessGroups(long poolId); + void deleteStorageAccessGroups(long poolId); + List listDistinctStorageAccessGroups(String name, String keyword); +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolAndAccessGroupMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolAndAccessGroupMapDaoImpl.java new file mode 100644 index 000000000000..63e82b79748d --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolAndAccessGroupMapDaoImpl.java @@ -0,0 +1,105 @@ +// 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.storage.dao; + +import java.util.ArrayList; +import java.util.List; + +import com.cloud.storage.StoragePoolAndAccessGroupMapVO; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.TransactionLegacy; + +public class StoragePoolAndAccessGroupMapDaoImpl extends GenericDaoBase implements StoragePoolAndAccessGroupMapDao { + + protected final SearchBuilder StoragePoolAccessGroupSearch; + + public StoragePoolAndAccessGroupMapDaoImpl() { + StoragePoolAccessGroupSearch = createSearchBuilder(); + StoragePoolAccessGroupSearch.and("poolId", StoragePoolAccessGroupSearch.entity().getPoolId(), SearchCriteria.Op.EQ); + StoragePoolAccessGroupSearch.done(); + } + + @Override + public void persist(long poolId, List storageAccessGroups) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + + txn.start(); + SearchCriteria sc = StoragePoolAccessGroupSearch.create(); + sc.setParameters("poolId", poolId); + expunge(sc); + + for (String sag : storageAccessGroups) { + sag = sag.trim(); + if (sag.length() > 0) { + StoragePoolAndAccessGroupMapVO vo = new StoragePoolAndAccessGroupMapVO(poolId, sag); + persist(vo); + } + } + txn.commit(); + } + + @Override + public List getStorageAccessGroups(long poolId) { + SearchCriteria sc = StoragePoolAccessGroupSearch.create(); + sc.setParameters("poolId", poolId); + + List results = search(sc, null); + List storagePoolAccessGroups = new ArrayList(results.size()); + for (StoragePoolAndAccessGroupMapVO result : results) { + storagePoolAccessGroups.add(result.getStorageAccessGroup()); + } + + return storagePoolAccessGroups; + } + + @Override + public void deleteStorageAccessGroups(long poolId) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + txn.start(); + SearchCriteria sc = StoragePoolAccessGroupSearch.create(); + sc.setParameters("poolId", poolId); + expunge(sc); + txn.commit(); + } + + @Override + public List listDistinctStorageAccessGroups(String name, String keyword) { + GenericSearchBuilder searchBuilder = createSearchBuilder(String.class); + + searchBuilder.select(null, SearchCriteria.Func.DISTINCT, searchBuilder.entity().getStorageAccessGroup()); + searchBuilder.and("name", searchBuilder.entity().getStorageAccessGroup(), SearchCriteria.Op.EQ); + searchBuilder.and("keyword", searchBuilder.entity().getStorageAccessGroup(), SearchCriteria.Op.LIKE); + searchBuilder.done(); + + SearchCriteria sc = searchBuilder.create(); + + if (name != null) { + sc.setParameters("name", name); + } + + if (keyword != null) { + sc.setParameters("keyword", "%" + keyword + "%"); + } + + return customSearch(sc, null); + } + +} diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java index 2d9656b5b0ac..a3baa3b4cb06 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolDetailsDaoImpl.java @@ -30,6 +30,8 @@ import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao; import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import com.cloud.utils.Pair; + public class StoragePoolDetailsDaoImpl extends ResourceDetailsDaoBase implements StoragePoolDetailsDao, ScopedConfigStorage { @Inject @@ -57,4 +59,17 @@ public void addDetail(long resourceId, String key, String value, boolean display } super.addDetail(new StoragePoolDetailVO(resourceId, key, value, display)); } + + @Override + public Pair getParentScope(long id) { + StoragePoolVO pool = _storagePoolDao.findById(id); + if (pool != null) { + if (pool.getClusterId() != null) { + return new Pair<>(getScope().getParent(), pool.getClusterId()); + } else { + return new Pair<>(ConfigKey.Scope.Zone, pool.getDataCenterId()); + } + } + return null; + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java index 639c2571541d..94e13ba2d56e 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDao.java @@ -30,6 +30,8 @@ public interface StoragePoolHostDao extends GenericDao public StoragePoolHostVO findByPoolHost(long poolId, long hostId); + List findByLocalPath(String path); + List listByHostStatus(long poolId, Status hostStatus); List findHostsConnectedToPools(List poolIds); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java index 5a466af348c3..55b5668bbc17 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/StoragePoolHostDaoImpl.java @@ -45,6 +45,7 @@ public class StoragePoolHostDaoImpl extends GenericDaoBase PoolSearch; protected final SearchBuilder HostSearch; protected final SearchBuilder PoolHostSearch; + protected final SearchBuilder LocalPathSearch; protected SearchBuilder poolNotInClusterSearch; @@ -77,6 +78,9 @@ public StoragePoolHostDaoImpl() { PoolHostSearch.and("host_id", PoolHostSearch.entity().getHostId(), SearchCriteria.Op.EQ); PoolHostSearch.done(); + LocalPathSearch = createSearchBuilder(); + LocalPathSearch.and("local_path", LocalPathSearch.entity().getLocalPath(), SearchCriteria.Op.EQ); + LocalPathSearch.done(); } @PostConstruct @@ -117,6 +121,13 @@ public StoragePoolHostVO findByPoolHost(long poolId, long hostId) { return findOneIncludingRemovedBy(sc); } + @Override + public List findByLocalPath(String path) { + SearchCriteria sc = LocalPathSearch.create(); + sc.setParameters("local_path", path); + return listBy(sc); + } + @Override public List listByHostStatus(long poolId, Status hostStatus) { TransactionLegacy txn = TransactionLegacy.currentTxn(); diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java index c751f81f9273..aec06d6d0003 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDao.java @@ -58,6 +58,8 @@ public interface VMTemplateDao extends GenericDao, StateDao< public List listInZoneByState(long dataCenterId, VirtualMachineTemplate.State... states); + public List listTemplateIsoByArchVnfAndZone(Long dataCenterId, CPU.CPUArch arch, Boolean isIso, Boolean isVnf); + public List listAllActive(); public List listByState(VirtualMachineTemplate.State... states); @@ -92,11 +94,18 @@ public interface VMTemplateDao extends GenericDao, StateDao< List listByParentTemplatetId(long parentTemplatetId); - VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch); + VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch); List findTemplatesLinkedToUserdata(long userdataId); List listByIds(List ids); List listIdsByTemplateTag(String tag); + + List listIdsByExtensionId(long extensionId); + + VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType, + CPU.CPUArch arch, String urlPathSuffix); + + VMTemplateVO findByAccountAndName(Long accountId, String templateName); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java index 45dffea76214..8c6e3fe0983f 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VMTemplateDaoImpl.java @@ -17,6 +17,7 @@ package com.cloud.storage.dao; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; @@ -99,7 +100,6 @@ public class VMTemplateDaoImpl extends GenericDaoBase implem private SearchBuilder PublicIsoSearch; private SearchBuilder UserIsoSearch; private GenericSearchBuilder CountTemplatesByAccount; - // private SearchBuilder updateStateSearch; private SearchBuilder AllFieldsSearch; protected SearchBuilder ParentTemplateIdSearch; private SearchBuilder InactiveUnremovedTmpltSearch; @@ -245,13 +245,17 @@ public List listReadyTemplates() { @Override - public VMTemplateVO findLatestTemplateByName(String name, CPU.CPUArch arch) { + public VMTemplateVO findLatestTemplateByName(String name, HypervisorType hypervisorType, CPU.CPUArch arch) { SearchBuilder sb = createSearchBuilder(); sb.and("name", sb.entity().getName(), SearchCriteria.Op.EQ); + sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ); sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ); sb.done(); SearchCriteria sc = sb.create(); sc.setParameters("name", name); + if (hypervisorType != null) { + sc.setParameters("hypervisorType", hypervisorType); + } if (arch != null) { sc.setParameters("arch", arch); } @@ -403,12 +407,6 @@ public boolean configure(String name, Map params) throws Configu CountTemplatesByAccount.and("state", CountTemplatesByAccount.entity().getState(), SearchCriteria.Op.EQ); CountTemplatesByAccount.done(); - // updateStateSearch = this.createSearchBuilder(); - // updateStateSearch.and("id", updateStateSearch.entity().getId(), Op.EQ); - // updateStateSearch.and("state", updateStateSearch.entity().getState(), Op.EQ); - // updateStateSearch.and("updatedCount", updateStateSearch.entity().getUpdatedCount(), Op.EQ); - // updateStateSearch.done(); - AllFieldsSearch = createSearchBuilder(); AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), SearchCriteria.Op.EQ); AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ); @@ -521,6 +519,48 @@ public List listInZoneByState(long dataCenterId, VirtualMachineTem return listBy(sc); } + @Override + public List listTemplateIsoByArchVnfAndZone(Long dataCenterId, CPU.CPUArch arch, Boolean isIso, + Boolean isVnf) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.select(null, Func.DISTINCT, sb.entity().getGuestOSId()); + sb.and("state", sb.entity().getState(), SearchCriteria.Op.IN); + sb.and("type", sb.entity().getTemplateType(), SearchCriteria.Op.IN); + sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ); + if (isIso != null) { + sb.and("isIso", sb.entity().getFormat(), isIso ? SearchCriteria.Op.EQ : SearchCriteria.Op.NEQ); + } + if (dataCenterId != null) { + SearchBuilder templateZoneSearch = _templateZoneDao.createSearchBuilder(); + templateZoneSearch.and("removed", templateZoneSearch.entity().getRemoved(), SearchCriteria.Op.NULL); + templateZoneSearch.and("zoneId", templateZoneSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + sb.join("templateZoneSearch", templateZoneSearch, templateZoneSearch.entity().getTemplateId(), + sb.entity().getId(), JoinBuilder.JoinType.INNER); + templateZoneSearch.done(); + } + sb.done(); + SearchCriteria sc = sb.create(); + List types = new ArrayList<>(Arrays.asList(TemplateType.USER, TemplateType.BUILTIN, + TemplateType.PERHOST)); + if (isVnf == null) { + types.add(TemplateType.VNF); + } else if (isVnf) { + types = Collections.singletonList(TemplateType.VNF); + } + sc.setParameters("type", types.toArray()); + sc.setParameters("state", VirtualMachineTemplate.State.Active); + if (dataCenterId != null) { + sc.setJoinParameters("templateZoneSearch", "zoneId", dataCenterId); + } + if (arch != null) { + sc.setParameters("arch", arch); + } + if (isIso != null) { + sc.setParameters("isIso", ImageFormat.ISO); + } + return customSearch(sc, null); + } + @Override public List listAllActive() { SearchCriteria sc = ActiveTmpltSearch.create(); @@ -803,6 +843,48 @@ public List listIdsByTemplateTag(String tag) { return customSearchIncludingRemoved(sc, null); } + @Override + public List listIdsByExtensionId(long extensionId) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.selectFields(sb.entity().getId()); + sb.and("extensionId", sb.entity().getExtensionId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("extensionId", extensionId); + return customSearch(sc, null); + } + + @Override + public VMTemplateVO findActiveSystemTemplateByHypervisorArchAndUrlPath(HypervisorType hypervisorType, + CPU.CPUArch arch, String urlPathSuffix) { + if (StringUtils.isBlank(urlPathSuffix)) { + return null; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("templateType", sb.entity().getTemplateType(), SearchCriteria.Op.EQ); + sb.and("hypervisorType", sb.entity().getHypervisorType(), SearchCriteria.Op.EQ); + sb.and("arch", sb.entity().getArch(), SearchCriteria.Op.EQ); + sb.and("urlPathSuffix", sb.entity().getUrl(), SearchCriteria.Op.LIKE); + sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("templateType", TemplateType.SYSTEM); + if (hypervisorType != null) { + sc.setParameters("hypervisorType", hypervisorType); + } + if (arch != null) { + sc.setParameters("arch", arch); + } + sc.setParameters("urlPathSuffix", "%" + urlPathSuffix); + sc.setParameters("state", VirtualMachineTemplate.State.Active); + Filter filter = new Filter(VMTemplateVO.class, "id", false, null, 1L); + List templates = listBy(sc, filter); + if (CollectionUtils.isNotEmpty(templates)) { + return templates.get(0); + } + return null; + } + @Override public boolean updateState( com.cloud.template.VirtualMachineTemplate.State currentState, @@ -863,4 +945,12 @@ public boolean updateState( } return rows > 0; } + + @Override + public VMTemplateVO findByAccountAndName(Long accountId, String templateName) { + SearchCriteria sc = NameAccountIdSearch.create(); + sc.setParameters("name", templateName); + sc.setParameters("accountId", accountId); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java index 83f027195185..4cd9a8e23bfc 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDao.java @@ -46,6 +46,8 @@ public interface VolumeDao extends GenericDao, StateDao findByInstanceAndType(long id, Volume.Type vType); + List findByInstanceAndNotStates(long id, Volume.State...states); + List findIncludingRemovedByInstanceAndType(long id, Volume.Type vType); List findNonDestroyedVolumesByInstanceIdAndPoolId(long instanceId, long poolId); @@ -109,6 +111,17 @@ public interface VolumeDao extends GenericDao, StateDao listVolumesByPassphraseId(long passphraseId); + /** + * List volumes with passphrase_id for migration to KMS + * + * @param zoneId Zone ID (required) + * @param accountId Account ID filter (optional, null for all accounts) + * @param domainId Domain ID filter (optional, null for all domains) + * @param limit Maximum number of volumes to return + * @return list of volumes that need migration + */ + Pair, Integer> listVolumesForKMSMigration(Long zoneId, Long accountId, Long domainId, Integer limit); + /** * Gets the Total Primary Storage space allocated for an account * @@ -164,4 +177,18 @@ public interface VolumeDao extends GenericDao, StateDao findByKmsWrappedKeyId(Long kmsWrappedKeyId); } diff --git a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java index d480ce6a0b8f..1d5ff5e93402 100644 --- a/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/storage/dao/VolumeDaoImpl.java @@ -29,6 +29,7 @@ import org.apache.cloudstack.reservation.ReservationVO; import org.apache.cloudstack.reservation.dao.ReservationDao; +import org.apache.cloudstack.kms.dao.KMSWrappedKeyDao; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; @@ -49,6 +50,7 @@ import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Func; @@ -73,16 +75,20 @@ public class VolumeDaoImpl extends GenericDaoBase implements Vol private final SearchBuilder storeAndInstallPathSearch; private final SearchBuilder volumeIdSearch; protected GenericSearchBuilder CountByAccount; + protected final SearchBuilder ExternalUuidSearch; protected GenericSearchBuilder primaryStorageSearch; protected GenericSearchBuilder primaryStorageSearch2; protected GenericSearchBuilder secondaryStorageSearch; private final SearchBuilder poolAndPathSearch; final GenericSearchBuilder CountByOfferingId; + private final SearchBuilder kmsMigrationSearch; @Inject ReservationDao reservationDao; @Inject ResourceTagDao tagsDao; + @Inject + KMSWrappedKeyDao kmsWrappedKeyDao; // need to account for zone-wide primary storage where storage_pool has // null-value pod and cluster, where hypervisor information is stored in @@ -206,6 +212,17 @@ public List findByInstanceAndType(long id, Type vType) { return listBy(sc); } + @Override + public List findByInstanceAndNotStates(long id, Volume.State...states) { + SearchBuilder sb = createSearchBuilder(); + sb.and("instanceId", sb.entity().getInstanceId(), Op.EQ); + sb.and("state", sb.entity().getState(), Op.NIN); + SearchCriteria sc = sb.create(); + sc.setParameters("instanceId", id); + sc.setParameters("state", (Object[]) states); + return listBy(sc); + } + @Override public List findIncludingRemovedByInstanceAndType(long id, Type vType) { SearchCriteria sc = AllFieldsSearch.create(); @@ -398,6 +415,9 @@ public VolumeDaoImpl() { AllFieldsSearch.and("name", AllFieldsSearch.entity().getName(), Op.EQ); AllFieldsSearch.and("passphraseId", AllFieldsSearch.entity().getPassphraseId(), Op.EQ); AllFieldsSearch.and("iScsiName", AllFieldsSearch.entity().get_iScsiName(), Op.EQ); + AllFieldsSearch.and("path", AllFieldsSearch.entity().getPath(), Op.EQ); + AllFieldsSearch.and("kmsKeyId", AllFieldsSearch.entity().getKmsKeyId(), Op.EQ); + AllFieldsSearch.and("kmsWrappedKeyId", AllFieldsSearch.entity().getKmsWrappedKeyId(), Op.EQ); AllFieldsSearch.done(); RootDiskStateSearch = createSearchBuilder(); @@ -457,6 +477,10 @@ public VolumeDaoImpl() { CountByAccount.and("idNIN", CountByAccount.entity().getId(), Op.NIN); CountByAccount.done(); + ExternalUuidSearch = createSearchBuilder(); + ExternalUuidSearch.and("externalUuid", ExternalUuidSearch.entity().getExternalUuid(), Op.EQ); + ExternalUuidSearch.done(); + primaryStorageSearch = createSearchBuilder(SumCount.class); primaryStorageSearch.select("sum", Func.SUM, primaryStorageSearch.entity().getSize()); primaryStorageSearch.and("accountId", primaryStorageSearch.entity().getAccountId(), Op.EQ); @@ -510,6 +534,13 @@ public VolumeDaoImpl() { CountByOfferingId.select(null, Func.COUNT, CountByOfferingId.entity().getId()); CountByOfferingId.and("diskOfferingId", CountByOfferingId.entity().getDiskOfferingId(), Op.EQ); CountByOfferingId.done(); + + kmsMigrationSearch = createSearchBuilder(); + kmsMigrationSearch.and("passphraseId", kmsMigrationSearch.entity().getPassphraseId(), Op.NNULL); + kmsMigrationSearch.and("zoneId", kmsMigrationSearch.entity().getDataCenterId(), Op.EQ); + kmsMigrationSearch.and("accountId", kmsMigrationSearch.entity().getAccountId(), Op.EQ); + kmsMigrationSearch.and("domainId", kmsMigrationSearch.entity().getDomainId(), Op.EQ); + kmsMigrationSearch.done(); } @Override @@ -730,6 +761,21 @@ public List listVolumesByPassphraseId(long passphraseId) { return listBy(sc); } + @Override + public Pair, Integer> listVolumesForKMSMigration(Long zoneId, Long accountId, Long domainId, Integer limit) { + SearchCriteria sc = kmsMigrationSearch.create(); + + Filter filter = new Filter(limit); + sc.setParameters("zoneId", zoneId); + if (accountId != null) { + sc.setParameters("accountId", accountId); + } + if (domainId != null) { + sc.setParameters("domainId", domainId); + } + return searchAndCount(sc, filter); + } + @Override @DB public boolean remove(Long id) { @@ -738,6 +784,17 @@ public boolean remove(Long id) { logger.debug(String.format("Removing volume %s from DB", id)); VolumeVO entry = findById(id); if (entry != null) { + // Clean up KMS wrapped key if volume was encrypted with KMS + if (entry.getKmsWrappedKeyId() != null) { + try { + kmsWrappedKeyDao.remove(entry.getKmsWrappedKeyId()); + logger.debug("Removed KMS wrapped key [id={}] for volume [id={}, uuid={}]", + entry.getKmsWrappedKeyId(), id, entry.getUuid()); + } catch (Exception e) { + logger.warn("Failed to remove KMS wrapped key [id={}] for volume [id={}, uuid={}]: {}", + entry.getKmsWrappedKeyId(), id, entry.getUuid(), e.getMessage(), e); + } + } tagsDao.removeByIdAndType(id, ResourceObjectType.Volume); } boolean result = super.remove(id); @@ -825,6 +882,7 @@ public void updateAndRemoveVolume(VolumeVO volume) { if (volume.getState() != Volume.State.Destroy) { volume.setState(Volume.State.Destroy); volume.setPoolId(null); + volume.setPoolType(null); volume.setInstanceId(null); update(volume.getId(), volume); remove(volume.getId()); @@ -909,6 +967,7 @@ public List searchRemovedByVms(List vmIds, Long batchSize) { return searchIncludingRemoved(sc, filter, null, false); } + @Override public VolumeVO findOneByIScsiName(String iScsiName) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("iScsiName", iScsiName); @@ -922,4 +981,34 @@ public int getVolumeCountByOfferingId(long diskOfferingId) { List results = customSearch(sc, null); return results.get(0); } + + @Override + public VolumeVO findByLastIdAndState(long lastVolumeId, State ...states) { + QueryBuilder sc = QueryBuilder.create(VolumeVO.class); + sc.and(sc.entity().getLastId(), SearchCriteria.Op.EQ, lastVolumeId); + sc.and(sc.entity().getState(), SearchCriteria.Op.IN, (Object[]) states); + return sc.find(); + } + + @Override + public boolean existsWithKmsKey(long kmsKeyId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("kmsKeyId", kmsKeyId); + sc.setParameters("notDestroyed", Volume.State.Expunged, Volume.State.Destroy); + return findOneBy(sc) != null; + } + + public VolumeVO findByExternalUuid(String externalUuid) { + SearchCriteria sc = ExternalUuidSearch.create(); + sc.setParameters("externalUuid", externalUuid); + return findOneBy(sc); + } + + @Override + public List findByKmsWrappedKeyId(Long kmsWrappedKeyId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("kmsWrappedKeyId", kmsWrappedKeyId); + sc.setParameters("notDestroyed", Volume.State.Expunged); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java index bacb09b98793..5efaea40a943 100644 --- a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java +++ b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagDao.java @@ -20,11 +20,13 @@ import java.util.Map; import java.util.Set; +import org.apache.cloudstack.api.response.ResourceTagResponse; + import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.tags.ResourceTagVO; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDao; -import org.apache.cloudstack.api.response.ResourceTagResponse; public interface ResourceTagDao extends GenericDao { @@ -60,4 +62,13 @@ public interface ResourceTagDao extends GenericDao { void removeByResourceIdAndKey(long resourceId, ResourceObjectType resourceType, String key); List listByResourceUuid(String resourceUuid); + + List listByResourceTypeKeyPrefixAndOwners(ResourceObjectType resourceType, String key, + List accountIds, List domainIds, + Filter filter); + + ResourceTagVO findByResourceTypeKeyPrefixAndValue(ResourceObjectType resourceType, String key, String value); + + List listByResourceTypeIdAndKeyPrefix(ResourceObjectType resourceType, long resourceId, String key); + } diff --git a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java index cc9d99e6ab16..22c7b7b2ee5b 100644 --- a/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/tags/dao/ResourceTagsDaoImpl.java @@ -16,19 +16,22 @@ // under the License. package com.cloud.tags.dao; -import java.util.List; -import java.util.Set; -import java.util.Map; import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import org.apache.cloudstack.api.response.ResourceTagResponse; +import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; import com.cloud.tags.ResourceTagVO; +import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.SearchCriteria.Op; @@ -120,4 +123,62 @@ public List listByResourceUuid(String resourceUuid) { sc.setParameters("resourceUuid", resourceUuid); return listBy(sc); } + + @Override + public List listByResourceTypeKeyPrefixAndOwners(ResourceObjectType resourceType, String key, + List accountIds, List domainIds, + Filter filter) { + GenericSearchBuilder sb = createSearchBuilder(String.class); + sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getValue()); + sb.and("resourceType", sb.entity().getResourceType(), Op.EQ); + sb.and("key", sb.entity().getKey(), Op.LIKE); + boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds); + boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds); + if (accountIdsNotEmpty || domainIdsNotEmpty) { + sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN); + sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN); + sb.cp(); + } + sb.done(); + final SearchCriteria sc = sb.create(); + sc.setParameters("resourceType", resourceType); + sc.setParameters("key", key + "%"); + if (accountIdsNotEmpty) { + sc.setParameters("account", accountIds.toArray()); + } + if (domainIdsNotEmpty) { + sc.setParameters("domain", domainIds.toArray()); + } + return customSearch(sc, filter); + } + + @Override + public ResourceTagVO findByResourceTypeKeyPrefixAndValue(ResourceObjectType resourceType, String key, + String value) { + SearchBuilder sb = createSearchBuilder(); + sb.and("resourceType", sb.entity().getResourceType(), Op.EQ); + sb.and("key", sb.entity().getKey(), Op.LIKE); + sb.and("value", sb.entity().getValue(), Op.EQ); + sb.done(); + final SearchCriteria sc = sb.create(); + sc.setParameters("resourceType", resourceType); + sc.setParameters("key", key + "%"); + sc.setParameters("value", value); + return findOneBy(sc); + } + + @Override + public List listByResourceTypeIdAndKeyPrefix(ResourceObjectType resourceType, long resourceId, + String key) { + SearchBuilder sb = createSearchBuilder(); + sb.and("resourceType", sb.entity().getResourceType(), Op.EQ); + sb.and("resourceId", sb.entity().getResourceId(), Op.EQ); + sb.and("key", sb.entity().getKey(), Op.LIKE); + sb.done(); + final SearchCriteria sc = sb.create(); + sc.setParameters("resourceType", resourceType); + sc.setParameters("resourceId", resourceId); + sc.setParameters("key", key + "%"); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java b/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java index 03857137ded6..5c1a75046927 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/ConfigurationGroupsAggregator.java @@ -54,7 +54,7 @@ public ConfigurationGroupsAggregator() { public void updateConfigurationGroups() { LOG.debug("Updating configuration groups"); - List configs = configDao.listAllIncludingRemoved(); + List configs = configDao.searchPartialConfigurations(); if (CollectionUtils.isEmpty(configs)) { return; } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java index 0d9807dcb2df..c3a982aa70e5 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/DatabaseUpgradeChecker.java @@ -33,11 +33,10 @@ import javax.inject.Inject; -import com.cloud.utils.FileUtil; import org.apache.cloudstack.utils.CloudStackVersion; import org.apache.commons.lang3.StringUtils; -import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import com.cloud.upgrade.dao.DbUpgrade; import com.cloud.upgrade.dao.DbUpgradeSystemVmTemplate; @@ -91,8 +90,12 @@ import com.cloud.upgrade.dao.Upgrade42000to42010; import com.cloud.upgrade.dao.Upgrade42020to42030; import com.cloud.upgrade.dao.Upgrade42030to42040; +import com.cloud.upgrade.dao.Upgrade42040to42100; +import com.cloud.upgrade.dao.Upgrade42100to42200; +import com.cloud.upgrade.dao.Upgrade42200to42210; import com.cloud.upgrade.dao.Upgrade420to421; import com.cloud.upgrade.dao.Upgrade421to430; +import com.cloud.upgrade.dao.Upgrade42210to42300; import com.cloud.upgrade.dao.Upgrade430to440; import com.cloud.upgrade.dao.Upgrade431to440; import com.cloud.upgrade.dao.Upgrade432to440; @@ -121,6 +124,7 @@ import com.cloud.upgrade.dao.VersionDaoImpl; import com.cloud.upgrade.dao.VersionVO; import com.cloud.upgrade.dao.VersionVO.Step; +import com.cloud.utils.FileUtil; import com.cloud.utils.component.SystemIntegrityChecker; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.GlobalLock; @@ -240,6 +244,10 @@ public DatabaseUpgradeChecker() { .next("4.20.0.0", new Upgrade42000to42010()) .next("4.20.2.0", new Upgrade42020to42030()) .next("4.20.3.0", new Upgrade42030to42040()) + .next("4.20.4.0", new Upgrade42040to42100()) + .next("4.21.0.0", new Upgrade42100to42200()) + .next("4.22.0.0", new Upgrade42200to42210()) + .next("4.22.1.0", new Upgrade42210to42300()) .build(); } @@ -316,20 +324,20 @@ protected void upgrade(CloudStackVersion dbVersion, CloudStackVersion currentVer } protected void executeProcedureScripts() { - LOGGER.info(String.format("Executing Stored Procedure scripts that are under resource directory [%s].", PROCEDURES_DIRECTORY)); + LOGGER.info("Executing Stored Procedure scripts that are under resource directory [{}].", PROCEDURES_DIRECTORY); List filesPathUnderViewsDirectory = FileUtil.getFilesPathsUnderResourceDirectory(PROCEDURES_DIRECTORY); try (TransactionLegacy txn = TransactionLegacy.open("execute-procedure-scripts")) { Connection conn = txn.getConnection(); for (String filePath : filesPathUnderViewsDirectory) { - LOGGER.debug(String.format("Executing PROCEDURE script [%s].", filePath)); + LOGGER.debug("Executing PROCEDURE script [{}].", filePath); InputStream viewScript = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); runScript(conn, viewScript); } - LOGGER.info(String.format("Finished execution of PROCEDURE scripts that are under resource directory [%s].", PROCEDURES_DIRECTORY)); + LOGGER.info("Finished execution of PROCEDURE scripts that are under resource directory [{}].", PROCEDURES_DIRECTORY); } catch (SQLException e) { String message = String.format("Unable to execute PROCEDURE scripts due to [%s].", e.getMessage()); LOGGER.error(message, e); @@ -338,7 +346,7 @@ protected void executeProcedureScripts() { } private DbUpgrade[] executeUpgrades(CloudStackVersion dbVersion, CloudStackVersion currentVersion) { - LOGGER.info("Database upgrade must be performed from " + dbVersion + " to " + currentVersion); + LOGGER.info("Database upgrade must be performed from {} to {}", dbVersion, currentVersion); final DbUpgrade[] upgrades = calculateUpgradePath(dbVersion, currentVersion); @@ -351,8 +359,8 @@ private DbUpgrade[] executeUpgrades(CloudStackVersion dbVersion, CloudStackVersi private VersionVO executeUpgrade(DbUpgrade upgrade) { VersionVO version; - LOGGER.debug("Running upgrade " + upgrade.getClass().getSimpleName() + " to upgrade from " + upgrade.getUpgradableVersionRange()[0] + "-" + upgrade - .getUpgradableVersionRange()[1] + " to " + upgrade.getUpgradedVersion()); + LOGGER.debug("Running upgrade {} to upgrade from {}-{} to {}", upgrade.getClass().getSimpleName(), upgrade.getUpgradableVersionRange()[0], upgrade + .getUpgradableVersionRange()[1], upgrade.getUpgradedVersion()); TransactionLegacy txn = TransactionLegacy.open("Upgrade"); txn.start(); try { @@ -384,6 +392,9 @@ private VersionVO executeUpgrade(DbUpgrade upgrade) { } finally { txn.close(); } + if (upgrade.refreshPoolConnectionsAfterUpgrade()) { + TransactionLegacy.refreshConnections(TransactionLegacy.CLOUD_DB); + } return version; } @@ -392,8 +403,8 @@ private void executeUpgradeCleanup(DbUpgrade upgrade, VersionVO version) { // Run the corresponding '-cleanup.sql' script txn = TransactionLegacy.open("Cleanup"); try { - LOGGER.info("Cleanup upgrade " + upgrade.getClass().getSimpleName() + " to upgrade from " + upgrade.getUpgradableVersionRange()[0] + "-" + upgrade - .getUpgradableVersionRange()[1] + " to " + upgrade.getUpgradedVersion()); + LOGGER.info("Cleanup upgrade {} to upgrade from {}-{} to {}", upgrade.getClass().getSimpleName(), upgrade.getUpgradableVersionRange()[0], upgrade + .getUpgradableVersionRange()[1], upgrade.getUpgradedVersion()); txn.start(); Connection conn; @@ -408,7 +419,7 @@ private void executeUpgradeCleanup(DbUpgrade upgrade, VersionVO version) { if (scripts != null) { for (InputStream script : scripts) { runScript(conn, script); - LOGGER.debug("Cleanup script " + upgrade.getClass().getSimpleName() + " is executed successfully"); + LOGGER.debug("Cleanup script {} is executed successfully", upgrade.getClass().getSimpleName()); } } txn.commit(); @@ -418,27 +429,27 @@ private void executeUpgradeCleanup(DbUpgrade upgrade, VersionVO version) { version.setUpdated(new Date()); _dao.update(version.getId(), version); txn.commit(); - LOGGER.debug("Upgrade completed for version " + version.getVersion()); + LOGGER.debug("Upgrade completed for version {}", version.getVersion()); } finally { txn.close(); } } protected void executeViewScripts() { - LOGGER.info(String.format("Executing VIEW scripts that are under resource directory [%s].", VIEWS_DIRECTORY)); + LOGGER.info("Executing VIEW scripts that are under resource directory [{}].", VIEWS_DIRECTORY); List filesPathUnderViewsDirectory = FileUtil.getFilesPathsUnderResourceDirectory(VIEWS_DIRECTORY); try (TransactionLegacy txn = TransactionLegacy.open("execute-view-scripts")) { Connection conn = txn.getConnection(); for (String filePath : filesPathUnderViewsDirectory) { - LOGGER.debug(String.format("Executing VIEW script [%s].", filePath)); + LOGGER.debug("Executing VIEW script [{}].", filePath); InputStream viewScript = Thread.currentThread().getContextClassLoader().getResourceAsStream(filePath); runScript(conn, viewScript); } - LOGGER.info(String.format("Finished execution of VIEW scripts that are under resource directory [%s].", VIEWS_DIRECTORY)); + LOGGER.info("Finished execution of VIEW scripts that are under resource directory [{}].", VIEWS_DIRECTORY); } catch (SQLException e) { String message = String.format("Unable to execute VIEW scripts due to [%s].", e.getMessage()); LOGGER.error(message, e); @@ -502,10 +513,10 @@ protected void doUpgrades(GlobalLock lock) { String csVersion = parseSystemVmMetadata(); final CloudStackVersion sysVmVersion = CloudStackVersion.parse(csVersion); final CloudStackVersion currentVersion = CloudStackVersion.parse(currentVersionValue); - SystemVmTemplateRegistration.CS_MAJOR_VERSION = String.valueOf(sysVmVersion.getMajorRelease()) + "." + String.valueOf(sysVmVersion.getMinorRelease()); + SystemVmTemplateRegistration.CS_MAJOR_VERSION = sysVmVersion.getMajorRelease() + "." + sysVmVersion.getMinorRelease(); SystemVmTemplateRegistration.CS_TINY_VERSION = String.valueOf(sysVmVersion.getPatchRelease()); - LOGGER.info("DB version = " + dbVersion + " Code Version = " + currentVersion); + LOGGER.info("DB version = {} Code Version = {}", dbVersion, currentVersion); if (dbVersion.compareTo(currentVersion) > 0) { throw new CloudRuntimeException("Database version " + dbVersion + " is higher than management software version " + currentVersionValue); @@ -578,7 +589,7 @@ private void decryptInit(Connection conn) throws SQLException { ResultSet result = pstmt.executeQuery()) { if (result.next()) { String init = result.getString(1); - LOGGER.info("init = " + DBEncryptionUtil.decrypt(init)); + LOGGER.info("init = {}", DBEncryptionUtil.decrypt(init)); } } } @@ -609,21 +620,11 @@ public String getUpgradedVersion() { return upgradedVersion; } - @Override - public boolean supportsRollingUpgrade() { - return false; - } - @Override public InputStream[] getPrepareScripts() { return new InputStream[0]; } - @Override - public void performDataMigration(Connection conn) { - - } - @Override public InputStream[] getCleanupScripts() { return new InputStream[0]; 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 2e45d567357e..2acb4138d234 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/SystemVmTemplateRegistration.java @@ -28,7 +28,6 @@ import java.sql.Date; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -51,6 +50,7 @@ import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.utils.security.DigestHelper; +import org.apache.cloudstack.utils.server.ServerPropertiesUtil; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -62,6 +62,8 @@ import com.cloud.dc.dao.ClusterDaoImpl; import com.cloud.dc.dao.DataCenterDao; import com.cloud.dc.dao.DataCenterDaoImpl; +import com.cloud.dc.dao.DataCenterDetailsDao; +import com.cloud.dc.dao.DataCenterDetailsDaoImpl; import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.DataStoreRole; import com.cloud.storage.GuestOSVO; @@ -103,14 +105,20 @@ public class SystemVmTemplateRegistration { private static final String METADATA_FILE = TEMPLATES_PATH + METADATA_FILE_NAME; public static final String TEMPORARY_SECONDARY_STORE = "tmp"; private static final String PARTIAL_TEMPLATE_FOLDER = String.format("/template/tmpl/%d/", Account.ACCOUNT_ID_SYSTEM); - private static final String storageScriptsDir = "scripts/storage/secondary"; + protected static final String STORAGE_SCRIPTS_DIR = "scripts/storage/secondary"; private static final Integer OTHER_LINUX_ID = 99; - private static Integer LINUX_12_ID = 363; + protected static Integer LINUX_12_ID = 363; private static final Integer SCRIPT_TIMEOUT = 1800000; private static final Integer LOCK_WAIT_TIMEOUT = 1200; + protected static final String TEMPLATE_DOWNLOAD_URL_KEY = "downloadurl"; + protected static final String TEMPLATES_DOWNLOAD_REPOSITORY_KEY = "downloadrepository"; + protected static final String TEMPLATES_CUSTOM_DOWNLOAD_REPOSITORY_KEY = "system.vm.templates.download.repository"; protected static final List DOWNLOADABLE_TEMPLATE_ARCH_TYPES = Arrays.asList( + CPU.CPUArch.amd64, CPU.CPUArch.arm64 ); + protected static final String MINIMUM_SYSTEM_VM_VERSION_KEY = "minreq.sysvmtemplate.version"; + protected static final String DEFAULT_SYSTEM_VM_GUEST_OS_NAME = "Debian GNU/Linux 12 (64-bit)"; public static String CS_MAJOR_VERSION = null; public static String CS_TINY_VERSION = null; @@ -134,7 +142,9 @@ public class SystemVmTemplateRegistration { @Inject ConfigurationDao configurationDao; @Inject - private GuestOSDao guestOSDao; + DataCenterDetailsDao dataCenterDetailsDao; + @Inject + GuestOSDao guestOSDao; private String systemVmTemplateVersion; @@ -142,6 +152,7 @@ public class SystemVmTemplateRegistration { public SystemVmTemplateRegistration() { dataCenterDao = new DataCenterDaoImpl(); + dataCenterDetailsDao = new DataCenterDetailsDaoImpl(); vmTemplateDao = new VMTemplateDaoImpl(); vmTemplateZoneDao = new VMTemplateZoneDaoImpl(); templateDataStoreDao = new BasicTemplateDataStoreDaoImpl(); @@ -155,33 +166,14 @@ public SystemVmTemplateRegistration() { } /** - * Convenience constructor method to use when there is no system VM template change for a new version. + * Convenience constructor method to use when there is no system VM Template change for a new version. */ public SystemVmTemplateRegistration(String systemVmTemplateVersion) { this(); this.systemVmTemplateVersion = systemVmTemplateVersion; } - public static String getMountCommand(String nfsVersion, String device, String dir) { - String cmd = MOUNT_COMMAND_BASE; - if (StringUtils.isNotBlank(nfsVersion)) { - cmd = String.format("%s -o vers=%s", cmd, nfsVersion); - } - return String.format("%s %s %s", cmd, device, dir); - } - - public String getSystemVmTemplateVersion() { - if (StringUtils.isEmpty(systemVmTemplateVersion)) { - return String.format("%s.%s", CS_MAJOR_VERSION, CS_TINY_VERSION); - } - return systemVmTemplateVersion; - } - - public File getTempDownloadDir() { - return tempDownloadDir; - } - - private static class SystemVMTemplateDetails { + protected static class SystemVMTemplateDetails { Long id; String uuid; String name; @@ -312,7 +304,7 @@ public void setUpdated(Date updated) { } } - public static final List> hypervisorList = Arrays.asList( + protected static final List> AVAILABLE_SYSTEM_TEMPLATES_HYPERVISOR_ARCH_LIST = Arrays.asList( new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64), new Pair<>(Hypervisor.HypervisorType.VMware, CPU.CPUArch.amd64), @@ -322,9 +314,9 @@ public void setUpdated(Date updated) { new Pair<>(Hypervisor.HypervisorType.Ovm3, CPU.CPUArch.amd64) ); - public static final Map NewTemplateMap = new HashMap<>(); + protected static final List METADATA_TEMPLATE_LIST = new ArrayList<>(); - public static final Map RouterTemplateConfigurationNames = new HashMap<>() { + protected static final Map ROUTER_TEMPLATE_CONFIGURATION_NAMES = new HashMap<>() { { put(Hypervisor.HypervisorType.KVM, "router.template.kvm"); put(Hypervisor.HypervisorType.VMware, "router.template.vmware"); @@ -335,18 +327,7 @@ public void setUpdated(Date updated) { } }; - public static Map hypervisorGuestOsMap = new HashMap<>() { - { - put(Hypervisor.HypervisorType.KVM, LINUX_12_ID); - put(Hypervisor.HypervisorType.XenServer, OTHER_LINUX_ID); - put(Hypervisor.HypervisorType.VMware, OTHER_LINUX_ID); - put(Hypervisor.HypervisorType.Hyperv, LINUX_12_ID); - put(Hypervisor.HypervisorType.LXC, LINUX_12_ID); - put(Hypervisor.HypervisorType.Ovm3, LINUX_12_ID); - } - }; - - public static final Map hypervisorImageFormat = new HashMap() { + protected static final Map HYPERVISOR_IMAGE_FORMAT_MAP = new HashMap<>() { { put(Hypervisor.HypervisorType.KVM, ImageFormat.QCOW2); put(Hypervisor.HypervisorType.XenServer, ImageFormat.VHD); @@ -357,69 +338,27 @@ public void setUpdated(Date updated) { } }; - public boolean validateIfSeeded(TemplateDataStoreVO templDataStoreVO, String url, String path, String nfsVersion) { - String filePath = null; - try { - filePath = Files.createTempDirectory(TEMPORARY_SECONDARY_STORE).toString(); - if (filePath == null) { - throw new CloudRuntimeException("Failed to create temporary directory to mount secondary store"); - } - mountStore(url, filePath, nfsVersion); - int lastIdx = path.lastIndexOf(File.separator); - String partialDirPath = path.substring(0, lastIdx); - String templatePath = filePath + File.separator + partialDirPath; - File templateProps = new File(templatePath + "/template.properties"); - if (templateProps.exists()) { - Pair templateSizes = readTemplatePropertiesSizes(templatePath + "/template.properties"); - updateSeededTemplateDetails(templDataStoreVO.getTemplateId(), templDataStoreVO.getDataStoreId(), - templateSizes.first(), templateSizes.second()); - LOGGER.info("SystemVM Template already seeded, skipping registration"); - return true; - } - LOGGER.info("SystemVM Template not seeded"); - return false; - } catch (Exception e) { - LOGGER.error("Failed to verify if the Template is seeded", e); - throw new CloudRuntimeException("Failed to verify if the Template is seeded", e); - } finally { - unmountStore(filePath); - try { - Files.delete(Path.of(filePath)); - } catch (IOException e) { - LOGGER.error(String.format("Failed to delete temporary directory: %s", filePath)); - } + protected static Map hypervisorGuestOsMap = new HashMap<>() { + { + put(Hypervisor.HypervisorType.KVM, LINUX_12_ID); + put(Hypervisor.HypervisorType.XenServer, OTHER_LINUX_ID); + put(Hypervisor.HypervisorType.VMware, OTHER_LINUX_ID); + put(Hypervisor.HypervisorType.Hyperv, LINUX_12_ID); + put(Hypervisor.HypervisorType.LXC, LINUX_12_ID); + put(Hypervisor.HypervisorType.Ovm3, LINUX_12_ID); } + }; + + private static boolean isRunningInTest() { + return "true".equalsIgnoreCase(System.getProperty("test.mode")); } private static String getHypervisorArchLog(Hypervisor.HypervisorType hypervisorType, CPU.CPUArch arch) { StringBuilder sb = new StringBuilder("hypervisor: ").append(hypervisorType.name()); - if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { - sb.append(", arch: ").append(arch == null ? CPU.CPUArch.amd64.getType() : arch.getType()); - } + sb.append(", arch: ").append(arch == null ? CPU.CPUArch.amd64.getType() : arch.getType()); return sb.toString(); } - protected static String getHypervisorArchKey(Hypervisor.HypervisorType hypervisorType, CPU.CPUArch arch) { - if (Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { - return String.format("%s-%s", hypervisorType.name().toLowerCase(), - arch == null ? CPU.CPUArch.amd64.getType() : arch.getType()); - } - return hypervisorType.name().toLowerCase(); - } - - protected static MetadataTemplateDetails getMetadataTemplateDetails(Hypervisor.HypervisorType hypervisorType, - CPU.CPUArch arch) { - return NewTemplateMap.get(getHypervisorArchKey(hypervisorType, arch)); - } - - public VMTemplateVO getRegisteredTemplate(String templateName, CPU.CPUArch arch) { - return vmTemplateDao.findLatestTemplateByName(templateName, arch); - } - - private static boolean isRunningInTest() { - return "true".equalsIgnoreCase(System.getProperty("test.mode")); - } - /** * Attempts to determine the templates directory path by locating the metadata file. *

@@ -460,7 +399,170 @@ private static String fetchTemplatesPath() { throw new CloudRuntimeException(errMsg); } - private List getEligibleZoneIds() { + protected static void cleanupStore(Long templateId, String filePath) { + String destTempFolder = filePath + PARTIAL_TEMPLATE_FOLDER + String.valueOf(templateId); + try { + Files.deleteIfExists(Paths.get(destTempFolder)); + } catch (IOException e) { + LOGGER.error("Failed to cleanup mounted store at: {}", filePath, e); + } + } + + protected static Pair readTemplatePropertiesSizes(String path) { + File tmpFile = new File(path); + Long size = null; + Long physicalSize = 0L; + try (FileReader fr = new FileReader(tmpFile); BufferedReader brf = new BufferedReader(fr);) { + String line = null; + while ((line = brf.readLine()) != null) { + if (line.startsWith("size=")) { + physicalSize = Long.parseLong(line.split("=")[1]); + } else if (line.startsWith("virtualsize=")) { + size = Long.parseLong(line.split("=")[1]); + } + if (size == null) { + size = physicalSize; + } + } + } catch (IOException ex) { + LOGGER.warn("Failed to read from template.properties", ex); + } + return new Pair<>(size, physicalSize); + } + + protected static MetadataTemplateDetails getMetadataTemplateDetails(Hypervisor.HypervisorType hypervisorType, + CPU.CPUArch arch) { + return METADATA_TEMPLATE_LIST + .stream() + .filter(x -> Objects.equals(x.getHypervisorType(), hypervisorType) && + Objects.equals(x.getArch(), arch)) + .findFirst() + .orElse(null); + } + + protected static String getMetadataFilePath() { + return METADATA_FILE; + } + + protected static Ini.Section getMetadataSectionForHypervisorAndArch(Ini ini, + Hypervisor.HypervisorType hypervisorType, CPU.CPUArch arch) { + String key = String.format("%s-%s", hypervisorType.name().toLowerCase(), + arch.getType().toLowerCase()); + Ini.Section section = ini.get(key); + if (section == null && !Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + key = String.format("%s", hypervisorType.name().toLowerCase()); + section = ini.get(key); + } + return section; + } + + protected static String getMountCommand(String nfsVersion, String device, String dir) { + String cmd = MOUNT_COMMAND_BASE; + if (StringUtils.isNotBlank(nfsVersion)) { + cmd = String.format("%s -o vers=%s", cmd, nfsVersion); + } + return String.format("%s %s %s", cmd, device, dir); + } + + /** + * This method parses the metadata file consisting of the system VM Templates information + * @return the version of the system VM Template that is to be used. This is done in order + * to fallback on the latest available version of the system VM Template when there doesn't + * exist a template corresponding to the current code version. + */ + public static String parseMetadataFile() { + String metadataFilePath = getMetadataFilePath(); + String errMsg = String.format("Failed to parse system VM Template metadata file: %s", metadataFilePath); + final Ini ini = new Ini(); + try (FileReader reader = new FileReader(metadataFilePath)) { + ini.load(reader); + } catch (IOException e) { + LOGGER.error(errMsg, e); + throw new CloudRuntimeException(errMsg, e); + } + if (!ini.containsKey("default")) { + errMsg = String.format("%s as unable to default section", errMsg); + LOGGER.error(errMsg); + throw new CloudRuntimeException(errMsg); + } + Ini.Section defaultSection = ini.get("default"); + String defaultDownloadRepository = defaultSection.get(TEMPLATES_DOWNLOAD_REPOSITORY_KEY); + String customDownloadRepository = ServerPropertiesUtil.getProperty(TEMPLATES_CUSTOM_DOWNLOAD_REPOSITORY_KEY); + boolean updateCustomDownloadRepository = StringUtils.isNotBlank(customDownloadRepository) && + StringUtils.isNotBlank(defaultDownloadRepository); + for (Pair hypervisorTypeArchPair : AVAILABLE_SYSTEM_TEMPLATES_HYPERVISOR_ARCH_LIST) { + String key = String.format("%s-%s", hypervisorTypeArchPair.first().name().toLowerCase(), + hypervisorTypeArchPair.second().getType().toLowerCase()); + Ini.Section section = getMetadataSectionForHypervisorAndArch(ini, hypervisorTypeArchPair.first(), + hypervisorTypeArchPair.second()); + if (section == null) { + LOGGER.error("Failed to find details for {} in template metadata file: {}", + getHypervisorArchLog(hypervisorTypeArchPair.first(), hypervisorTypeArchPair.second()), + metadataFilePath); + continue; + } + String url = section.get(TEMPLATE_DOWNLOAD_URL_KEY); + if (StringUtils.isNotBlank(url) && updateCustomDownloadRepository) { + url = url.replaceFirst(defaultDownloadRepository.trim(), + customDownloadRepository.trim()); + LOGGER.debug("Updated download URL for {} using custom repository to {}", key, url); + } + METADATA_TEMPLATE_LIST.add(new MetadataTemplateDetails( + hypervisorTypeArchPair.first(), + section.get("templatename"), + section.get("filename"), + url, + section.get("checksum"), + hypervisorTypeArchPair.second(), + section.get("guestos"))); + } + return defaultSection.get("version").trim(); + } + + public static void mountStore(String storeUrl, String path, String nfsVersion) { + try { + if (storeUrl == null) { + return; + } + URI uri = new URI(UriUtils.encodeURIComponent(storeUrl)); + String host = uri.getHost(); + String mountPath = uri.getPath(); + Script.runSimpleBashScript(getMountCommand(nfsVersion, host + ":" + mountPath, path)); + } catch (Exception e) { + String msg = "NFS Store URL is not in the correct format"; + LOGGER.error(msg, e); + throw new CloudRuntimeException(msg, e); + } + } + + public static void unmountStore(String filePath) { + try { + LOGGER.info("Unmounting store"); + String umountCmd = String.format(UMOUNT_COMMAND, filePath); + Script.runSimpleBashScript(umountCmd); + try { + Files.deleteIfExists(Paths.get(filePath)); + } catch (IOException e) { + LOGGER.error(String.format("Failed to cleanup mounted store at: %s", filePath), e); + } + } catch (Exception e) { + String msg = String.format("Failed to unmount store mounted at %s", filePath); + LOGGER.error(msg, e); + throw new CloudRuntimeException(msg, e); + } + } + + protected File getTempDownloadDir() { + return tempDownloadDir; + } + + protected void readTemplateProperties(String path, SystemVMTemplateDetails details) { + Pair templateSizes = readTemplatePropertiesSizes(path); + details.setSize(templateSizes.first()); + details.setPhysicalSize(templateSizes.second()); + } + + protected List getEligibleZoneIds() { List zoneIds = new ArrayList<>(); List stores = imageStoreDao.findByProtocol("nfs"); for (ImageStoreVO store : stores) { @@ -484,20 +586,11 @@ protected Pair getNfsStoreInZone(Long zoneId) { return new Pair<>(url, storeId); } - public static void mountStore(String storeUrl, String path, String nfsVersion) { - try { - if (storeUrl == null) { - return; - } - URI uri = new URI(UriUtils.encodeURIComponent(storeUrl)); - String host = uri.getHost(); - String mountPath = uri.getPath(); - Script.runSimpleBashScript(getMountCommand(nfsVersion, host + ":" + mountPath, path)); - } catch (Exception e) { - String msg = "NFS Store URL is not in the correct format"; - LOGGER.error(msg, e); - throw new CloudRuntimeException(msg, e); + protected String getSystemVmTemplateVersion() { + if (StringUtils.isEmpty(systemVmTemplateVersion)) { + return String.format("%s.%s", CS_MAJOR_VERSION, CS_TINY_VERSION); } + return systemVmTemplateVersion; } private VMTemplateVO createTemplateObjectInDB(SystemVMTemplateDetails details) { @@ -513,7 +606,7 @@ private VMTemplateVO createTemplateObjectInDB(SystemVMTemplateDetails details) { template.setBits(64); template.setAccountId(Account.ACCOUNT_ID_SYSTEM); template.setUrl(details.getUrl()); - template.setChecksum(details.getChecksum()); + template.setChecksum(DigestHelper.prependAlgorithm(details.getChecksum())); template.setEnablePassword(false); template.setDisplayText(details.getName()); template.setFormat(details.getFormat()); @@ -527,7 +620,7 @@ private VMTemplateVO createTemplateObjectInDB(SystemVMTemplateDetails details) { return template; } - private VMTemplateZoneVO createOrUpdateTemplateZoneEntry(long zoneId, long templateId) { + protected VMTemplateZoneVO createOrUpdateTemplateZoneEntry(long zoneId, long templateId) { VMTemplateZoneVO templateZoneVO = vmTemplateZoneDao.findByZoneTemplate(zoneId, templateId); if (templateZoneVO == null) { templateZoneVO = new VMTemplateZoneVO(zoneId, templateId, new java.util.Date()); @@ -541,33 +634,37 @@ private VMTemplateZoneVO createOrUpdateTemplateZoneEntry(long zoneId, long templ return templateZoneVO; } - private void createCrossZonesTemplateZoneRefEntries(Long templateId) { + protected void createCrossZonesTemplateZoneRefEntries(Long templateId) { List dcs = dataCenterDao.listAll(); for (DataCenterVO dc : dcs) { VMTemplateZoneVO templateZoneVO = createOrUpdateTemplateZoneEntry(dc.getId(), templateId); if (templateZoneVO == null) { - throw new CloudRuntimeException(String.format("Failed to create template_zone_ref record for the systemVM Template (id: %s) and zone: %s", templateId, dc)); + throw new CloudRuntimeException(String.format("Failed to create template-zone record for the system " + + "VM Template (ID : %d) and zone: %s", templateId, dc)); } } } - private void createTemplateStoreRefEntry(SystemVMTemplateDetails details) { - TemplateDataStoreVO templateDataStoreVO = new TemplateDataStoreVO(details.storeId, details.getId(), details.getCreated(), 0, - VMTemplateStorageResourceAssoc.Status.NOT_DOWNLOADED, null, null, null, details.getInstallPath(), details.getUrl()); + protected void createTemplateStoreRefEntry(SystemVMTemplateDetails details) { + TemplateDataStoreVO templateDataStoreVO = new TemplateDataStoreVO(details.getStoreId(), details.getId(), + details.getCreated(), 0, VMTemplateStorageResourceAssoc.Status.NOT_DOWNLOADED, + null, null, null, details.getInstallPath(), details.getUrl()); templateDataStoreVO.setDataStoreRole(DataStoreRole.Image); templateDataStoreVO = templateDataStoreDao.persist(templateDataStoreVO); if (templateDataStoreVO == null) { - throw new CloudRuntimeException(String.format("Failed to create template_store_ref record for the systemVM Template for hypervisor: %s", details.getHypervisorType().name())); + throw new CloudRuntimeException(String.format("Failed to create template-store record for the system VM " + + "template (ID : %d) and store (ID: %d)", details.getId(), details.getStoreId())); } } - public void updateTemplateDetails(SystemVMTemplateDetails details) { + protected void updateTemplateDetails(SystemVMTemplateDetails details) { VMTemplateVO template = vmTemplateDao.findById(details.getId()); template.setSize(details.getSize()); template.setState(VirtualMachineTemplate.State.Active); vmTemplateDao.update(template.getId(), template); - TemplateDataStoreVO templateDataStoreVO = templateDataStoreDao.findByStoreTemplate(details.getStoreId(), template.getId()); + TemplateDataStoreVO templateDataStoreVO = templateDataStoreDao.findByStoreTemplate(details.getStoreId(), + template.getId()); templateDataStoreVO.setSize(details.getSize()); templateDataStoreVO.setPhysicalSize(details.getPhysicalSize()); templateDataStoreVO.setDownloadPercent(100); @@ -576,11 +673,11 @@ public void updateTemplateDetails(SystemVMTemplateDetails details) { templateDataStoreVO.setState(ObjectInDataStoreStateMachine.State.Ready); boolean updated = templateDataStoreDao.update(templateDataStoreVO.getId(), templateDataStoreVO); if (!updated) { - throw new CloudRuntimeException("Failed to update template_store_ref entry for registered systemVM Template"); + throw new CloudRuntimeException("Failed to update template-store record for registered system VM Template"); } } - public void updateSeededTemplateDetails(long templateId, long storeId, long size, long physicalSize) { + protected void updateSeededTemplateDetails(long templateId, long storeId, long size, long physicalSize) { VMTemplateVO template = vmTemplateDao.findById(templateId); template.setSize(size); vmTemplateDao.update(template.getId(), template); @@ -591,108 +688,77 @@ public void updateSeededTemplateDetails(long templateId, long storeId, long size templateDataStoreVO.setLastUpdated(new Date(DateUtil.currentGMTTime().getTime())); boolean updated = templateDataStoreDao.update(templateDataStoreVO.getId(), templateDataStoreVO); if (!updated) { - throw new CloudRuntimeException("Failed to update template_store_ref entry for seeded systemVM template"); + throw new CloudRuntimeException("Failed to update template-store record for seeded system VM Template"); } } - public void updateSystemVMEntries(Long templateId, Hypervisor.HypervisorType hypervisorType) { + protected void updateSystemVMEntries(Long templateId, Hypervisor.HypervisorType hypervisorType) { vmInstanceDao.updateSystemVmTemplateId(templateId, hypervisorType); } - private void updateSystemVmTemplateGuestOsId() { - String systemVmGuestOsName = "Debian GNU/Linux 12 (64-bit)"; // default + protected void updateHypervisorGuestOsMap() { try { - GuestOSVO guestOS = guestOSDao.findOneByDisplayName(systemVmGuestOsName); - if (guestOS != null) { - LOGGER.debug("Updating SystemVM Template Guest OS [{}] id", systemVmGuestOsName); - SystemVmTemplateRegistration.LINUX_12_ID = Math.toIntExact(guestOS.getId()); - hypervisorGuestOsMap.put(Hypervisor.HypervisorType.KVM, LINUX_12_ID); - hypervisorGuestOsMap.put(Hypervisor.HypervisorType.Hyperv, LINUX_12_ID); - hypervisorGuestOsMap.put(Hypervisor.HypervisorType.LXC, LINUX_12_ID); - hypervisorGuestOsMap.put(Hypervisor.HypervisorType.Ovm3, LINUX_12_ID); + GuestOSVO guestOS = guestOSDao.findOneByDisplayName(DEFAULT_SYSTEM_VM_GUEST_OS_NAME); + if (guestOS == null) { + LOGGER.warn("Couldn't find Guest OS by name [{}] to update system VM Template guest OS ID", + DEFAULT_SYSTEM_VM_GUEST_OS_NAME); + return; } + LOGGER.debug("Updating system VM Template guest OS [{}] ID", DEFAULT_SYSTEM_VM_GUEST_OS_NAME); + SystemVmTemplateRegistration.LINUX_12_ID = Math.toIntExact(guestOS.getId()); + hypervisorGuestOsMap.put(Hypervisor.HypervisorType.KVM, LINUX_12_ID); + hypervisorGuestOsMap.put(Hypervisor.HypervisorType.Hyperv, LINUX_12_ID); + hypervisorGuestOsMap.put(Hypervisor.HypervisorType.LXC, LINUX_12_ID); + hypervisorGuestOsMap.put(Hypervisor.HypervisorType.Ovm3, LINUX_12_ID); } catch (Exception e) { - LOGGER.warn("Couldn't update SystemVM Template Guest OS id, due to {}", e.getMessage()); + LOGGER.warn("Couldn't update System VM template guest OS ID, due to {}", e.getMessage()); } } - public void updateConfigurationParams(Map configParams) { - for (Map.Entry config : configParams.entrySet()) { - boolean updated = configurationDao.update(config.getKey(), config.getValue()); - if (!updated) { - throw new CloudRuntimeException(String.format("Failed to update configuration parameter %s", config.getKey())); - } + protected void updateConfigurationParams(Hypervisor.HypervisorType hypervisorType, String templateName, Long zoneId) { + String configName = ROUTER_TEMPLATE_CONFIGURATION_NAMES.get(hypervisorType); + boolean updated = configurationDao.update(configName, templateName); + if (!updated) { + throw new CloudRuntimeException(String.format("Failed to update configuration parameter %s", configName)); } - } - - private static Pair readTemplatePropertiesSizes(String path) { - File tmpFile = new File(path); - Long size = null; - Long physicalSize = 0L; - try (FileReader fr = new FileReader(tmpFile); BufferedReader brf = new BufferedReader(fr);) { - String line = null; - while ((line = brf.readLine()) != null) { - if (line.startsWith("size=")) { - physicalSize = Long.parseLong(line.split("=")[1]); - } else if (line.startsWith("virtualsize=")) { - size = Long.parseLong(line.split("=")[1]); - } - if (size == null) { - size = physicalSize; - } - } - } catch (IOException ex) { - LOGGER.warn("Failed to read from template.properties", ex); + if (zoneId != null) { + dataCenterDetailsDao.removeDetail(zoneId, configName); + } + updated = configurationDao.update(MINIMUM_SYSTEM_VM_VERSION_KEY, getSystemVmTemplateVersion()); + if (!updated) { + throw new CloudRuntimeException(String.format("Failed to update configuration parameter %s", configName)); + } + if (zoneId != null) { + dataCenterDetailsDao.removeDetail(zoneId, MINIMUM_SYSTEM_VM_VERSION_KEY); } - return new Pair<>(size, physicalSize); - } - - public static void readTemplateProperties(String path, SystemVMTemplateDetails details) { - Pair templateSizes = readTemplatePropertiesSizes(path); - details.setSize(templateSizes.first()); - details.setPhysicalSize(templateSizes.second()); } - private void updateTemplateTablesOnFailure(long templateId) { + protected void updateTemplateEntriesOnFailure(long templateId) { VMTemplateVO template = vmTemplateDao.createForUpdate(templateId); template.setState(VirtualMachineTemplate.State.Inactive); vmTemplateDao.update(template.getId(), template); vmTemplateDao.remove(templateId); - TemplateDataStoreVO templateDataStoreVO = templateDataStoreDao.findByTemplate(template.getId(), DataStoreRole.Image); - templateDataStoreDao.remove(templateDataStoreVO.getId()); - } - - public static void unmountStore(String filePath) { - try { - LOGGER.info("Unmounting store"); - String umountCmd = String.format(UMOUNT_COMMAND, filePath); - Script.runSimpleBashScript(umountCmd); - try { - Files.deleteIfExists(Paths.get(filePath)); - } catch (IOException e) { - LOGGER.error(String.format("Failed to cleanup mounted store at: %s", filePath), e); - } - } catch (Exception e) { - String msg = String.format("Failed to unmount store mounted at %s", filePath); - LOGGER.error(msg, e); - throw new CloudRuntimeException(msg, e); + TemplateDataStoreVO templateDataStoreVO = templateDataStoreDao.findByTemplate(template.getId(), + DataStoreRole.Image); + if (templateDataStoreVO == null) { + return; } + templateDataStoreDao.remove(templateDataStoreVO.getId()); } - private void setupTemplate(String templateName, Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch, - String destTempFolder) throws CloudRuntimeException { - String setupTmpltScript = Script.findScript(storageScriptsDir, "setup-sysvm-tmplt"); + protected void setupTemplateOnStore(String templateName, MetadataTemplateDetails templateDetails, + String destTempFolder) throws CloudRuntimeException { + String setupTmpltScript = Script.findScript(STORAGE_SCRIPTS_DIR, "setup-sysvm-tmplt"); if (setupTmpltScript == null) { - throw new CloudRuntimeException("Unable to find the createtmplt.sh"); + throw new CloudRuntimeException("Unable to find the setup-sysvm-tmplt script"); } Script scr = new Script(setupTmpltScript, SCRIPT_TIMEOUT, LOGGER); scr.add("-u", templateName); - MetadataTemplateDetails templateDetails = NewTemplateMap.get(getHypervisorArchKey(hypervisor, arch)); String filePath = StringUtils.isNotBlank(templateDetails.getDownloadedFilePath()) ? templateDetails.getDownloadedFilePath() : templateDetails.getDefaultFilePath(); scr.add("-f", filePath); - scr.add("-h", hypervisor.name().toLowerCase(Locale.ROOT)); + scr.add("-h", templateDetails.getHypervisorType().name().toLowerCase(Locale.ROOT)); scr.add("-d", destTempFolder); String result = scr.execute(); if (result != null) { @@ -702,17 +768,33 @@ private void setupTemplate(String templateName, Hypervisor.HypervisorType hyperv } } - private Long performTemplateRegistrationOperations(Hypervisor.HypervisorType hypervisor, - String name, CPU.CPUArch arch, String url, String checksum, ImageFormat format, long guestOsId, - Long storeId, Long templateId, String filePath, TemplateDataStoreVO templateDataStoreVO) { + /** + * Register or update a system VM Template record and seed it on the target store. + * + * @param name display name of the template + * @param templateDetails metadata for the template + * @param url download URL of the template + * @param checksum expected checksum of the template file + * @param format image format of the template + * @param guestOsId guest OS id + * @param storeId target image store id + * @param templateId existing template id if present, otherwise {@code null} + * @param filePath temporary mount path for the store + * @param templateDataStoreVO existing template-store mapping; may be {@code null} + * @return the id of the template that was created or updated + */ + protected Long performTemplateRegistrationOperations(String name, MetadataTemplateDetails templateDetails, + String url, String checksum, ImageFormat format, long guestOsId, Long storeId, Long templateId, + String filePath, TemplateDataStoreVO templateDataStoreVO) { String templateName = UUID.randomUUID().toString(); Date created = new Date(DateUtil.currentGMTTime().getTime()); - SystemVMTemplateDetails details = new SystemVMTemplateDetails(templateName, name, created, - url, checksum, format, (int) guestOsId, hypervisor, arch, storeId); + SystemVMTemplateDetails details = new SystemVMTemplateDetails(templateName, name, created, url, checksum, + format, (int) guestOsId, templateDetails.getHypervisorType(), templateDetails.getArch(), storeId); if (templateId == null) { VMTemplateVO template = createTemplateObjectInDB(details); if (template == null) { - throw new CloudRuntimeException(String.format("Failed to register Template for hypervisor: %s", hypervisor.name())); + throw new CloudRuntimeException(String.format("Failed to register Template for hypervisor: %s", + templateDetails.getHypervisorType().name())); } templateId = template.getId(); } @@ -721,153 +803,126 @@ private Long performTemplateRegistrationOperations(Hypervisor.HypervisorType hyp details.setId(templateId); String destTempFolderName = String.valueOf(templateId); String destTempFolder = filePath + PARTIAL_TEMPLATE_FOLDER + destTempFolderName; - details.setInstallPath(PARTIAL_TEMPLATE_FOLDER + destTempFolderName + File.separator + templateName + "." + hypervisorImageFormat.get(hypervisor).getFileExtension()); + details.setInstallPath(String.format("%s%s%s%s.%s", PARTIAL_TEMPLATE_FOLDER, destTempFolderName, + File.separator, templateName, + HYPERVISOR_IMAGE_FORMAT_MAP.get(templateDetails.getHypervisorType()).getFileExtension())); if (templateDataStoreVO == null) { createTemplateStoreRefEntry(details); } - setupTemplate(templateName, hypervisor, arch, destTempFolder); + setupTemplateOnStore(templateName, templateDetails, destTempFolder); readTemplateProperties(destTempFolder + "/template.properties", details); details.setUpdated(new Date(DateUtil.currentGMTTime().getTime())); updateTemplateDetails(details); return templateId; } - public void registerTemplate(Hypervisor.HypervisorType hypervisor, String name, Long storeId, - VMTemplateVO templateVO, TemplateDataStoreVO templateDataStoreVO, String filePath) { + /** + * Add an existing system VM Template to a secondary image store and update related DB entries. + * + * @param templateVO the existing VM template (must not be null) + * @param templateDetails the metadata details of the template to be added + * @param templateDataStoreVO optional existing template-store mapping; may be null + * @param zoneId zone id where the operation is performed + * @param storeId target image store id + * @param filePath temporary mount path for the store + * @throws CloudRuntimeException on failure; the method attempts rollback/cleanup + */ + protected void addExistingTemplateToStore(VMTemplateVO templateVO, MetadataTemplateDetails templateDetails, + TemplateDataStoreVO templateDataStoreVO, long zoneId, Long storeId, String filePath) { try { - performTemplateRegistrationOperations(hypervisor, name, templateVO.getArch(), templateVO.getUrl(), + performTemplateRegistrationOperations(templateVO.getName(), templateDetails, templateVO.getUrl(), templateVO.getChecksum(), templateVO.getFormat(), templateVO.getGuestOSId(), storeId, templateVO.getId(), filePath, templateDataStoreVO); } catch (Exception e) { - String errMsg = String.format("Failed to register Template for hypervisor: %s", hypervisor); + String errMsg = String.format("Failed to add %s to store ID: %d, zone ID: %d", templateVO, storeId, zoneId); LOGGER.error(errMsg, e); - updateTemplateTablesOnFailure(templateVO.getId()); cleanupStore(templateVO.getId(), filePath); throw new CloudRuntimeException(errMsg, e); } } - public void registerTemplateForNonExistingEntries(Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch, - String name, Pair storeUrlAndId, String filePath) { + /** + * Registers a new system VM Template for the given hypervisor/arch when no existing template is present. + * + * @param name the name of the new template + * @param templateDetails the metadata details of the template to be registered + * @param zoneId the zone id for which the new template should be seeded + * @param storeId the store id on which the new template will be seeded + * @param filePath temporary mount path for the store + * @throws CloudRuntimeException on failure; the method attempts rollback/cleanup + */ + protected void registerNewTemplate(String name, MetadataTemplateDetails templateDetails, long zoneId, Long storeId, + String filePath) { Long templateId = null; + Hypervisor.HypervisorType hypervisor = templateDetails.getHypervisorType(); try { - MetadataTemplateDetails templateDetails = getMetadataTemplateDetails(hypervisor, arch); - templateId = performTemplateRegistrationOperations(hypervisor, name, - templateDetails.getArch(), templateDetails.getUrl(), - templateDetails.getChecksum(), hypervisorImageFormat.get(hypervisor), - hypervisorGuestOsMap.get(hypervisor), storeUrlAndId.second(), null, filePath, null); - Map configParams = new HashMap<>(); - configParams.put(RouterTemplateConfigurationNames.get(hypervisor), templateDetails.getName()); - configParams.put("minreq.sysvmtemplate.version", getSystemVmTemplateVersion()); - updateConfigurationParams(configParams); + templateId = performTemplateRegistrationOperations(name, templateDetails, templateDetails.getUrl(), + templateDetails.getChecksum(), HYPERVISOR_IMAGE_FORMAT_MAP.get(hypervisor), + hypervisorGuestOsMap.get(hypervisor), storeId, null, filePath, null); + updateConfigurationParams(hypervisor, name, zoneId); updateSystemVMEntries(templateId, hypervisor); } catch (Exception e) { String errMsg = String.format("Failed to register Template for hypervisor: %s", hypervisor); LOGGER.error(errMsg, e); if (templateId != null) { - updateTemplateTablesOnFailure(templateId); + updateTemplateEntriesOnFailure(templateId); cleanupStore(templateId, filePath); } throw new CloudRuntimeException(errMsg, e); } } - protected void validateTemplateFileForHypervisorAndArch(Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch) { + /** + * Validate presence and integrity of metadata and local template file for the given hypervisor/arch. + * + * @param hypervisor target hypervisor type + * @param arch target CPU architecture + * @return validated MetadataTemplateDetails + * @throws CloudRuntimeException if template is not available, missing, or checksum validation fails + */ + protected MetadataTemplateDetails getValidatedTemplateDetailsForHypervisorAndArch( + Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch) { + if (!AVAILABLE_SYSTEM_TEMPLATES_HYPERVISOR_ARCH_LIST.contains(new Pair<>(hypervisor, arch))) { + throw new CloudRuntimeException("No system VM Template available for the given hypervisor and arch"); + } MetadataTemplateDetails templateDetails = getMetadataTemplateDetails(hypervisor, arch); + if (templateDetails == null) { + throw new CloudRuntimeException("No template details found for the given hypervisor and arch"); + } File templateFile = getTemplateFile(templateDetails); if (templateFile == null) { throw new CloudRuntimeException("Failed to find local template file"); } - if (isTemplateFileChecksumDifferent(templateDetails, templateFile)) { + if (templateDetails.isFileChecksumDifferent(templateFile)) { throw new CloudRuntimeException("Checksum failed for local template file"); } - } - - public void validateAndRegisterTemplate(Hypervisor.HypervisorType hypervisor, String name, Long storeId, - VMTemplateVO templateVO, TemplateDataStoreVO templateDataStoreVO, String filePath) { - validateTemplateFileForHypervisorAndArch(hypervisor, templateVO.getArch()); - registerTemplate(hypervisor, name, storeId, templateVO, templateDataStoreVO, filePath); - } - - public void validateAndRegisterTemplateForNonExistingEntries(Hypervisor.HypervisorType hypervisor, - CPU.CPUArch arch, String name, Pair storeUrlAndId, String filePath) { - validateTemplateFileForHypervisorAndArch(hypervisor, arch); - registerTemplateForNonExistingEntries(hypervisor, arch, name, storeUrlAndId, filePath); - } - - protected static String getMetadataFilePath() { - return METADATA_FILE; + return templateDetails; } /** - * This method parses the metadata file consisting of the systemVM templates information - * @return the version of the systemvm template that is to be used. This is done in order - * to fallback on the latest available version of the systemVM template when there doesn't - * exist a template corresponding to the current code version. + * Return the local template file. Downloads it if not present locally and url is present. + * + * @param templateDetails template metadata; may set `downloadedFilePath` + * @return the template {@code File} on disk, or {@code null} if not found/downloaded */ - public static String parseMetadataFile() { - String metadataFilePath = getMetadataFilePath(); - String errMsg = String.format("Failed to parse systemVM Template metadata file: %s", metadataFilePath); - final Ini ini = new Ini(); - try (FileReader reader = new FileReader(metadataFilePath)) { - ini.load(reader); - } catch (IOException e) { - LOGGER.error(errMsg, e); - throw new CloudRuntimeException(errMsg, e); - } - if (!ini.containsKey("default")) { - errMsg = String.format("%s as unable to default section", errMsg); - LOGGER.error(errMsg); - throw new CloudRuntimeException(errMsg); - } - for (Pair hypervisorType : hypervisorList) { - String key = getHypervisorArchKey(hypervisorType.first(), hypervisorType.second()); - Ini.Section section = ini.get(key); - if (section == null) { - LOGGER.error("Failed to find details for {} in template metadata file: {}", - key, metadataFilePath); - continue; - } - NewTemplateMap.put(key, new MetadataTemplateDetails( - hypervisorType.first(), - section.get("templatename"), - section.get("filename"), - section.get("downloadurl"), - section.get("checksum"), - hypervisorType.second(), - section.get("guestos"))); - } - Ini.Section defaultSection = ini.get("default"); - return defaultSection.get("version").trim(); - } - - - private static void cleanupStore(Long templateId, String filePath) { - String destTempFolder = filePath + PARTIAL_TEMPLATE_FOLDER + String.valueOf(templateId); - try { - Files.deleteIfExists(Paths.get(destTempFolder)); - } catch (IOException e) { - LOGGER.error(String.format("Failed to cleanup mounted store at: %s", filePath), e); - } - } - protected File getTemplateFile(MetadataTemplateDetails templateDetails) { File templateFile = new File(templateDetails.getDefaultFilePath()); if (templateFile.exists()) { return templateFile; } LOGGER.debug("{} is not present", templateFile.getAbsolutePath()); - if (DOWNLOADABLE_TEMPLATE_ARCH_TYPES.contains(templateDetails.getArch()) && - StringUtils.isNotBlank(templateDetails.getUrl())) { + if (StringUtils.isNotBlank(templateDetails.getUrl())) { LOGGER.debug("Downloading the template file {} for {}", templateDetails.getUrl(), templateDetails.getHypervisorArchLog()); Path path = Path.of(TEMPLATES_PATH); if (!Files.isWritable(path)) { - templateFile = new File(tempDownloadDir, templateDetails.getFilename()); + templateFile = new File(getTempDownloadDir(), templateDetails.getFilename()); } if (!templateFile.exists() && !HttpUtils.downloadFileWithProgress(templateDetails.getUrl(), templateFile.getAbsolutePath(), LOGGER)) { + LOGGER.error("Failed to download template for {} using url: {}", + templateDetails.getHypervisorArchLog(), templateDetails.getUrl()); return null; } templateDetails.setDownloadedFilePath(templateFile.getAbsolutePath()); @@ -875,32 +930,28 @@ protected File getTemplateFile(MetadataTemplateDetails templateDetails) { return templateFile; } - protected boolean isTemplateFileChecksumDifferent(MetadataTemplateDetails templateDetails, File templateFile) { - String templateChecksum = DigestHelper.calculateChecksum(templateFile); - if (!templateChecksum.equals(templateDetails.getChecksum())) { - LOGGER.error("Checksum {} for file {} does not match checksum {} from metadata", - templateChecksum, templateFile, templateDetails.getChecksum()); - return true; - } - return false; - } - - protected void validateTemplates(List> hypervisorsArchInUse) { + /** + * Validate that templates for the provided hypervisor/architecture pairs which are in use and are valid. + * + * If a template is missing or validation fails for any required pair, a + * {@link CloudRuntimeException} is thrown to abort the upgrade. If system VM Template for a hypervisor/arch is + * not considered available then validation is skipped for that pair. + * + * @param hypervisorArchList list of hypervisor/architecture pairs to validate + */ + protected void validateTemplates(List> hypervisorArchList) { boolean templatesFound = true; - for (Pair hypervisorArch : hypervisorsArchInUse) { - MetadataTemplateDetails matchedTemplate = getMetadataTemplateDetails(hypervisorArch.first(), - hypervisorArch.second()); - if (matchedTemplate == null) { - templatesFound = false; - break; - } - File tempFile = getTemplateFile(matchedTemplate); - if (tempFile == null) { - LOGGER.warn("Failed to download template for {}, moving ahead", - matchedTemplate.getHypervisorArchLog()); + for (Pair hypervisorArch : hypervisorArchList) { + if (!AVAILABLE_SYSTEM_TEMPLATES_HYPERVISOR_ARCH_LIST.contains(hypervisorArch)) { + LOGGER.info("No system VM Template available for {}. Skipping validation.", + getHypervisorArchLog(hypervisorArch.first(), hypervisorArch.second())); continue; } - if (isTemplateFileChecksumDifferent(matchedTemplate, tempFile)) { + try { + getValidatedTemplateDetailsForHypervisorAndArch(hypervisorArch.first(), hypervisorArch.second()); + } catch (CloudRuntimeException e) { + LOGGER.error("Validation failed for {}: {}", + getHypervisorArchLog(hypervisorArch.first(), hypervisorArch.second()), e.getMessage()); templatesFound = false; break; } @@ -912,12 +963,22 @@ protected void validateTemplates(List storeUrlAndId = getNfsStoreInZone(zoneId); String nfsVersion = getNfsVersion(storeUrlAndId.second()); - mountStore(storeUrlAndId.first(), filePath, nfsVersion); + mountStore(storeUrlAndId.first(), storeMountPath, nfsVersion); List> hypervisorArchList = - clusterDao.listDistinctHypervisorsArchAcrossClusters(zoneId); + clusterDao.listDistinctHypervisorsAndArchExcludingExternalType(zoneId); for (Pair hypervisorArch : hypervisorArchList) { Hypervisor.HypervisorType hypervisorType = hypervisorArch.first(); MetadataTemplateDetails templateDetails = getMetadataTemplateDetails(hypervisorType, @@ -925,7 +986,8 @@ protected void registerTemplatesForZone(long zoneId, String filePath) { if (templateDetails == null) { continue; } - VMTemplateVO templateVO = getRegisteredTemplate(templateDetails.getName(), templateDetails.getArch()); + VMTemplateVO templateVO = getRegisteredTemplate(templateDetails.getName(), + templateDetails.getHypervisorType(), templateDetails.getArch(), templateDetails.getUrl()); if (templateVO != null) { TemplateDataStoreVO templateDataStoreVO = templateDataStoreDao.findByStoreTemplate(storeUrlAndId.second(), templateVO.getId()); @@ -935,22 +997,22 @@ protected void registerTemplatesForZone(long zoneId, String filePath) { continue; } } - registerTemplate(hypervisorType, templateDetails.getName(), storeUrlAndId.second(), templateVO, - templateDataStoreVO, filePath); - updateRegisteredTemplateDetails(templateVO.getId(), templateDetails); + addExistingTemplateToStore(templateVO, templateDetails, templateDataStoreVO, zoneId, + storeUrlAndId.second(), storeMountPath); + updateRegisteredTemplateDetails(templateVO.getId(), templateDetails, zoneId); continue; } - registerTemplateForNonExistingEntries(hypervisorType, templateDetails.getArch(), templateDetails.getName(), - storeUrlAndId, filePath); + registerNewTemplate(templateDetails.getName(), templateDetails, zoneId, storeUrlAndId.second(), + storeMountPath); } } - public void registerTemplates(List> hypervisorsArchInUse) { + protected void registerTemplates(List> hypervisorsArchInUse) { GlobalLock lock = GlobalLock.getInternLock("UpgradeDatabase-Lock"); try { LOGGER.info("Grabbing lock to register Templates."); if (!lock.lock(LOCK_WAIT_TIMEOUT)) { - throw new CloudRuntimeException("Unable to acquire lock to register SystemVM Template."); + throw new CloudRuntimeException("Unable to acquire lock to register system VM Template."); } try { validateTemplates(hypervisorsArchInUse); @@ -970,13 +1032,13 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { unmountStore(filePath); } catch (Exception e) { unmountStore(filePath); - throw new CloudRuntimeException("Failed to register SystemVM Template. Upgrade failed"); + throw new CloudRuntimeException("Failed to register system VM Template. Upgrade Failed"); } } } }); } catch (Exception e) { - throw new CloudRuntimeException("Failed to register SystemVM Template. Upgrade failed"); + throw new CloudRuntimeException("Failed to register system VM Template. Upgrade Failed"); } } finally { lock.unlock(); @@ -984,7 +1046,18 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { } } - private void updateRegisteredTemplateDetails(Long templateId, MetadataTemplateDetails templateDetails) { + /** + * Update the DB record for an existing template to mark it as a system template, + * set the guest OS (if resolvable), and propagate the change to system VM entries + * and related configuration for the template's hypervisor. + * + * @param templateId id of the template to update + * @param templateDetails metadata used to update the template record + * @param zoneId zone id whose per-zone details (if any) should be cleared; may be null + * @throws CloudRuntimeException if updating the template record fails + */ + protected void updateRegisteredTemplateDetails(Long templateId, MetadataTemplateDetails templateDetails, + Long zoneId) { VMTemplateVO templateVO = vmTemplateDao.findById(templateId); templateVO.setTemplateType(Storage.TemplateType.SYSTEM); GuestOSVO guestOS = guestOSDao.findOneByDisplayName(templateDetails.getGuestOs()); @@ -993,86 +1066,225 @@ private void updateRegisteredTemplateDetails(Long templateId, MetadataTemplateDe } boolean updated = vmTemplateDao.update(templateVO.getId(), templateVO); if (!updated) { - String errMsg = String.format("updateSystemVmTemplates:Exception while updating Template with id %s to be marked as 'system'", templateId); + String errMsg = String.format("Exception while updating template with id %s to be marked as 'system'", + templateId); LOGGER.error(errMsg); throw new CloudRuntimeException(errMsg); } Hypervisor.HypervisorType hypervisorType = templateDetails.getHypervisorType(); updateSystemVMEntries(templateId, hypervisorType); - // Change value of global configuration parameter router.template.* for the corresponding hypervisor and minreq.sysvmtemplate.version for the ACS version - Map configParams = new HashMap<>(); - configParams.put(RouterTemplateConfigurationNames.get(hypervisorType), templateDetails.getName()); - configParams.put("minreq.sysvmtemplate.version", getSystemVmTemplateVersion()); - updateConfigurationParams(configParams); + updateConfigurationParams(hypervisorType, templateVO.getName(), zoneId); } - private void updateTemplateUrlChecksumAndGuestOsId(VMTemplateVO templateVO, MetadataTemplateDetails templateDetails) { + protected void updateTemplateUrlChecksumAndGuestOsId(VMTemplateVO templateVO, + MetadataTemplateDetails templateDetails) { templateVO.setUrl(templateDetails.getUrl()); - templateVO.setChecksum(templateDetails.getChecksum()); + templateVO.setChecksum(DigestHelper.prependAlgorithm(templateDetails.getChecksum())); GuestOSVO guestOS = guestOSDao.findOneByDisplayName(templateDetails.getGuestOs()); if (guestOS != null) { templateVO.setGuestOSId(guestOS.getId()); } boolean updated = vmTemplateDao.update(templateVO.getId(), templateVO); if (!updated) { - String errMsg = String.format("updateSystemVmTemplates:Exception while updating 'url' and 'checksum' for hypervisor type %s", templateDetails.getHypervisorType()); + String errMsg = String.format("Exception while updating 'url' and 'checksum' for hypervisor type %s", + templateDetails.getHypervisorType()); LOGGER.error(errMsg); throw new CloudRuntimeException(errMsg); } } - protected boolean registerOrUpdateSystemVmTemplate(MetadataTemplateDetails templateDetails, - List> hypervisorsInUse) { - LOGGER.debug("Updating System VM template for {}", templateDetails.getHypervisorArchLog()); - VMTemplateVO registeredTemplate = getRegisteredTemplate(templateDetails.getName(), templateDetails.getArch()); - // change template type to SYSTEM + /** + * Updates or registers the system VM Template for the given hypervisor/arch if not already present. + * Returns true if a new template was registered. + * If there is an existing system VM Template for the given hypervisor/arch, its details are updated. + * If no existing template is found, new templates are registered for the valid hypervisor/arch which are in use. + */ + protected boolean updateOrRegisterSystemVmTemplate(MetadataTemplateDetails templateDetails, + List> hypervisorArchInUse) { + String systemVmTemplateLog = String.format("%s system VM Template for %s", getSystemVmTemplateVersion(), + templateDetails.getHypervisorArchLog()); + LOGGER.debug("Registering or updating {}", systemVmTemplateLog, + templateDetails.getHypervisorArchLog()); + VMTemplateVO registeredTemplate = getRegisteredTemplate(templateDetails.getName(), + templateDetails.getHypervisorType(), templateDetails.getArch(), templateDetails.getUrl()); if (registeredTemplate != null) { - updateRegisteredTemplateDetails(registeredTemplate.getId(), templateDetails); - } else { - boolean isHypervisorArchMatchMetadata = hypervisorsInUse.stream() - .anyMatch(p -> p.first().equals(templateDetails.getHypervisorType()) - && Objects.equals(p.second(), templateDetails.getArch())); - if (isHypervisorArchMatchMetadata) { - try { - registerTemplates(hypervisorsInUse); - return true; - } catch (final Exception e) { - throw new CloudRuntimeException(String.format("Failed to register %s templates for hypervisors: [%s]. " + - "Cannot upgrade system VMs", - getSystemVmTemplateVersion(), - StringUtils.join(hypervisorsInUse.stream() - .map(x -> getHypervisorArchKey(x.first(), x.second())) - .collect(Collectors.toList()), ",")), e); - } - } else { - LOGGER.warn("Cannot upgrade {} system VM template for {} as it is not used, not failing upgrade", - getSystemVmTemplateVersion(), templateDetails.getHypervisorArchLog()); - VMTemplateVO templateVO = vmTemplateDao.findLatestTemplateByTypeAndHypervisorAndArch( - templateDetails.getHypervisorType(), templateDetails.getArch(), Storage.TemplateType.SYSTEM); - if (templateVO != null) { - updateTemplateUrlChecksumAndGuestOsId(templateVO, templateDetails); - } + LOGGER.info("{} is already registered, updating details for: {}", + systemVmTemplateLog, templateDetails.getHypervisorArchLog(), registeredTemplate); + updateRegisteredTemplateDetails(registeredTemplate.getId(), templateDetails, null); + return false; + } + boolean isHypervisorArchMatchMetadata = hypervisorArchInUse.stream() + .anyMatch(p -> p.first().equals(templateDetails.getHypervisorType()) + && Objects.equals(p.second(), templateDetails.getArch())); + if (!isHypervisorArchMatchMetadata) { + LOGGER.warn("Skipping upgrading {} as it is not used, not failing upgrade", + getSystemVmTemplateVersion(), templateDetails.getHypervisorArchLog()); + VMTemplateVO templateVO = vmTemplateDao.findLatestTemplateByTypeAndHypervisorAndArch( + templateDetails.getHypervisorType(), templateDetails.getArch(), Storage.TemplateType.SYSTEM); + if (templateVO != null) { + updateTemplateUrlChecksumAndGuestOsId(templateVO, templateDetails); } + return false; + } + try { + registerTemplates(hypervisorArchInUse); + return true; + } catch (final Exception e) { + throw new CloudRuntimeException(String.format("Failed to register %s templates for hypervisors: [%s]. " + + "Cannot upgrade system VMs", + getSystemVmTemplateVersion(), + StringUtils.join(hypervisorArchInUse.stream() + .map(x -> String.format("%s-%s", x.first().name(), x.second().name())) + .collect(Collectors.toList()), ",")), e); } - return false; } + /** + * Return NFS version for the store: store-specific config if present + * or global config if absent. Returns null if not set. + */ + protected String getNfsVersion(long storeId) { + final String configKey = "secstorage.nfs.version"; + final Map storeDetails = imageStoreDetailsDao.getDetails(storeId); + if (storeDetails != null && storeDetails.containsKey(configKey)) { + return storeDetails.get(configKey); + } + ConfigurationVO globalNfsVersion = configurationDao.findByName(configKey); + if (globalNfsVersion != null) { + return globalNfsVersion.getValue(); + } + return null; + } + + /** + * Validate metadata for the given template's hypervisor/arch and add the existing template + * to the specified secondary store. On success, database entries are created/updated. + * + * @param templateVO template to add + * @param templateDataStoreVO existing template-store mapping; may be null + * @param zoneId zone id where the operation is performed + * @param storeId target image store id + * @param filePath temporary mount path for the store + * @throws CloudRuntimeException on failure; the method attempts rollback/cleanup + */ + public void validateAndAddTemplateToStore(VMTemplateVO templateVO, TemplateDataStoreVO templateDataStoreVO, + long zoneId, long storeId, String filePath) { + MetadataTemplateDetails templateDetails = getValidatedTemplateDetailsForHypervisorAndArch( + templateVO.getHypervisorType(), templateVO.getArch()); + addExistingTemplateToStore(templateVO, templateDetails, templateDataStoreVO, zoneId, storeId, filePath); + } + + /** + * Validate metadata for the given hypervisor/arch and register a new system VM Template + * on the specified store and zone. Creates DB entries and seeds the template on the store. + * + * @param hypervisor hypervisor type + * @param arch cpu architecture + * @param name template name to register + * @param zoneId zone id where the operation is performed + * @param storeId target image store id + * @param filePath temporary mount path for the store + * @throws CloudRuntimeException on failure; the method attempts rollback/cleanup + */ + public void validateAndRegisterNewTemplate(Hypervisor.HypervisorType hypervisor, CPU.CPUArch arch, String name, + long zoneId, long storeId, String filePath) { + MetadataTemplateDetails templateDetails = getValidatedTemplateDetailsForHypervisorAndArch(hypervisor, arch); + registerNewTemplate(name, templateDetails, zoneId, storeId, filePath); + } + + /** + * Check whether the template at the given `path` on NFS `url` is already seeded. + * If found, updates DB with sizes and returns true; otherwise returns false. + * + * @throws CloudRuntimeException on any error + */ + public boolean validateIfSeeded(TemplateDataStoreVO templDataStoreVO, String url, String path, String nfsVersion) { + String filePath = null; + try { + filePath = Files.createTempDirectory(TEMPORARY_SECONDARY_STORE).toString(); + if (filePath == null) { + throw new CloudRuntimeException("Failed to create temporary directory to mount secondary store"); + } + mountStore(url, filePath, nfsVersion); + int lastIdx = path.lastIndexOf(File.separator); + String partialDirPath = path.substring(0, lastIdx); + String templatePath = filePath + File.separator + partialDirPath; + File templateProps = new File(templatePath + "/template.properties"); + if (templateProps.exists()) { + Pair templateSizes = readTemplatePropertiesSizes(templatePath + "/template.properties"); + updateSeededTemplateDetails(templDataStoreVO.getTemplateId(), templDataStoreVO.getDataStoreId(), + templateSizes.first(), templateSizes.second()); + LOGGER.info("System VM template already seeded, skipping registration"); + return true; + } + LOGGER.info("System VM template not seeded"); + return false; + } catch (Exception e) { + LOGGER.error("Failed to verify if the template is seeded", e); + throw new CloudRuntimeException("Failed to verify if the template is seeded", e); + } finally { + unmountStore(filePath); + try { + Files.delete(Path.of(filePath)); + } catch (IOException e) { + LOGGER.error("Failed to delete temporary directory: {}", filePath); + } + } + } + + /** + * Finds a registered system VM Template matching the provided criteria. + * + *

The method first attempts to locate the latest template by {@code templateName}, + * {@code hypervisorType} and {@code arch}. If none is found and a non-blank {@code url} + * is provided, it falls back to searching for an active system template by the + * URL path segment (the substring after the last '/' in the URL).

+ * + * @param templateName the template name to search for + * @param hypervisorType the hypervisor type + * @param arch the CPU architecture + * @param url optional download URL used as a fallback; may be {@code null} or blank + * @return the matching {@code VMTemplateVO} if found; {@code null} otherwise + */ + public VMTemplateVO getRegisteredTemplate(String templateName, Hypervisor.HypervisorType hypervisorType, + CPU.CPUArch arch, String url) { + VMTemplateVO registeredTemplate = vmTemplateDao.findLatestTemplateByName(templateName, hypervisorType, arch); + if (registeredTemplate == null && StringUtils.isNotBlank(url)) { + String urlPath = url.substring(url.lastIndexOf("/") + 1); + LOGGER.debug("No template found by name, falling back to search existing SYSTEM template by " + + "urlPath: {}, hypervisor: {}, arch:{}", urlPath, hypervisorType, arch); + registeredTemplate = vmTemplateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath(hypervisorType, arch, + urlPath); + } + LOGGER.debug("Found existing registered template for hypervisor: {}, arch: {}: {}", hypervisorType, + arch, registeredTemplate); + return registeredTemplate; + } + + /** + * Update or register system VM Templates based on metadata. + * + * Runs the registration logic inside a database transaction: obtains the + * set of hypervisors/architectures in use, iterates over metadata entries + * and attempts to register or update each template. + * + * @param conn retained for compatibility with callers (not used directly) + */ public void updateSystemVmTemplates(final Connection conn) { - LOGGER.debug("Updating System Vm template IDs"); - updateSystemVmTemplateGuestOsId(); + LOGGER.debug("Updating System VM templates"); + updateHypervisorGuestOsMap(); Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(final TransactionStatus status) { List> hypervisorsInUse; try { - hypervisorsInUse = clusterDao.listDistinctHypervisorsArchAcrossClusters(null); + hypervisorsInUse = clusterDao.listDistinctHypervisorsAndArchExcludingExternalType(null); } catch (final Exception e) { throw new CloudRuntimeException("Exception while getting hypervisor types from clusters", e); } - Collection templateEntries = NewTemplateMap.values(); - for (MetadataTemplateDetails templateDetails : templateEntries) { + for (MetadataTemplateDetails templateDetails : METADATA_TEMPLATE_LIST) { try { - if (registerOrUpdateSystemVmTemplate(templateDetails, hypervisorsInUse)) { + if (updateOrRegisterSystemVmTemplate(templateDetails, hypervisorsInUse)) { break; } } catch (final Exception e) { @@ -1081,24 +1293,11 @@ public void doInTransactionWithoutResult(final TransactionStatus status) { throw new CloudRuntimeException(errMsg, e); } } - LOGGER.debug("Updating System Vm Template IDs Complete"); + LOGGER.debug("Updating System VM Templates Complete"); } }); } - public String getNfsVersion(long storeId) { - final String configKey = "secstorage.nfs.version"; - final Map storeDetails = imageStoreDetailsDao.getDetails(storeId); - if (storeDetails != null && storeDetails.containsKey(configKey)) { - return storeDetails.get(configKey); - } - ConfigurationVO globalNfsVersion = configurationDao.findByName(configKey); - if (globalNfsVersion != null) { - return globalNfsVersion.getValue(); - } - return null; - } - protected static class MetadataTemplateDetails { private final Hypervisor.HypervisorType hypervisorType; private final String name; @@ -1160,6 +1359,16 @@ public String getDefaultFilePath() { return TEMPLATES_PATH + filename; } + public boolean isFileChecksumDifferent(File file) { + String fileChecksum = DigestHelper.calculateChecksum(file); + if (!fileChecksum.equals(getChecksum())) { + LOGGER.error("Checksum {} for file {} does not match checksum {} from metadata", + fileChecksum, file, getChecksum()); + return true; + } + return false; + } + public String getHypervisorArchLog() { return SystemVmTemplateRegistration.getHypervisorArchLog(hypervisorType, arch); } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/DatabaseAccessObject.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/DatabaseAccessObject.java index 0b973d195deb..223d7a466376 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/DatabaseAccessObject.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/DatabaseAccessObject.java @@ -87,6 +87,36 @@ public boolean columnExists(Connection conn, String tableName, String columnName return columnExists; } + public String getColumnType(Connection conn, String tableName, String columnName) { + try (PreparedStatement pstmt = conn.prepareStatement(String.format("DESCRIBE %s %s", tableName, columnName));){ + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + return rs.getString("Type"); + } + } catch (SQLException e) { + logger.warn("Type for column {} can not be retrieved in {} ignoring exception: {}", columnName, tableName, e.getMessage()); + } + return null; + } + + public void addColumn(Connection conn, String tableName, String columnName, String columnDefinition) { + try (PreparedStatement pstmt = conn.prepareStatement(String.format("ALTER TABLE %s ADD COLUMN %s %s", tableName, columnName, columnDefinition));){ + pstmt.executeUpdate(); + logger.debug("Column {} is added successfully from the table {}", columnName, tableName); + } catch (SQLException e) { + logger.warn("Unable to add column {} to table {} due to exception", columnName, tableName, e); + } + } + + public void changeColumn(Connection conn, String tableName, String oldColumnName, String newColumnName, String columnDefinition) { + try (PreparedStatement pstmt = conn.prepareStatement(String.format("ALTER TABLE %s CHANGE COLUMN %s %s %s", tableName, oldColumnName, newColumnName, columnDefinition));){ + pstmt.executeUpdate(); + logger.debug("Column {} is changed successfully to {} from the table {}", oldColumnName, newColumnName, tableName); + } catch (SQLException e) { + logger.warn("Unable to add column {} to {} from the table {} due to exception", oldColumnName, newColumnName, tableName, e); + } + } + public String generateIndexName(String tableName, String... columnName) { return String.format("i_%s__%s", tableName, StringUtils.join(columnName, "__")); } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgrade.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgrade.java index 02c401c81558..fa0d0506ac13 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgrade.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgrade.java @@ -16,6 +16,8 @@ // under the License. package com.cloud.upgrade.dao; +import com.cloud.utils.exception.CloudRuntimeException; + import java.io.InputStream; import java.sql.Connection; @@ -24,18 +26,45 @@ public interface DbUpgrade { String getUpgradedVersion(); - boolean supportsRollingUpgrade(); + default boolean supportsRollingUpgrade() { + return false; + } /** * @return the script to prepare the database schema for the * data migration step. */ - InputStream[] getPrepareScripts(); + default InputStream[] getPrepareScripts() { + String fromVersion = getUpgradableVersionRange()[0]; + String toVersion = getUpgradableVersionRange()[1]; + final String scriptFile = String.format("META-INF/db/schema-%sto%s.sql", fromVersion.replace(".", ""), toVersion.replace(".", "")); + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[]{script}; + } /** * Performs the actual data migration. */ - void performDataMigration(Connection conn); + default void performDataMigration(Connection conn) { + } + + default InputStream[] getCleanupScripts() { + String fromVersion = getUpgradableVersionRange()[0]; + String toVersion = getUpgradableVersionRange()[1]; + final String scriptFile = String.format("META-INF/db/schema-%sto%s-cleanup.sql", fromVersion.replace(".", ""), toVersion.replace(".", "")); + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[]{script}; + } - InputStream[] getCleanupScripts(); + default boolean refreshPoolConnectionsAfterUpgrade() { + return false; + } } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java index 2f90422adf8b..be073fcce770 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/DbUpgradeUtils.java @@ -58,4 +58,20 @@ public static void dropTableColumnsIfExist(Connection conn, String tableName, Li } } + public static String getTableColumnType(Connection conn, String tableName, String columnName) { + return dao.getColumnType(conn, tableName, columnName); + } + + public static void addTableColumnIfNotExist(Connection conn, String tableName, String columnName, String columnDefinition) { + if (!dao.columnExists(conn, tableName, columnName)) { + dao.addColumn(conn, tableName, columnName, columnDefinition); + } + } + + public static void changeTableColumnIfNotExist(Connection conn, String tableName, String oldColumnName, String newColumnName, String columnDefinition) { + if (dao.columnExists(conn, tableName, oldColumnName)) { + dao.changeColumn(conn, tableName, oldColumnName, newColumnName, columnDefinition); + } + } + } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade2214to30.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade2214to30.java index 524b6a34893b..d4cdbcb9707d 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade2214to30.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade2214to30.java @@ -77,8 +77,6 @@ public void performDataMigration(Connection conn) { encryptData(conn); // drop keys dropKeysIfExist(conn); - //update template ID for system Vms - //updateSystemVms(conn); This is not required as system template update is handled during 4.2 upgrade // update domain network ref updateDomainNetworkRef(conn); // update networks that use redundant routers to the new network offering diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade302to40.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade302to40.java index aa427252585f..bd8ddaa7c498 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade302to40.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade302to40.java @@ -62,7 +62,6 @@ public InputStream[] getPrepareScripts() { @Override public void performDataMigration(Connection conn) { - //updateVmWareSystemVms(conn); This is not required as system template update is handled during 4.2 upgrade correctVRProviders(conn); correctMultiplePhysicaNetworkSetups(conn); addHostDetailsUniqueKey(conn); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade304to305.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade304to305.java index 3167dd8115b4..38dc90b460dd 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade304to305.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade304to305.java @@ -65,7 +65,6 @@ public void performDataMigration(Connection conn) { addVpcProvider(conn); updateRouterNetworkRef(conn); fixZoneUsingExternalDevices(conn); -// updateSystemVms(conn); fixForeignKeys(conn); encryptClusterDetails(conn); } @@ -81,54 +80,6 @@ public InputStream[] getCleanupScripts() { return new InputStream[] {script}; } - private void updateSystemVms(Connection conn) { - PreparedStatement pstmt = null; - ResultSet rs = null; - boolean VMware = false; - try { - pstmt = conn.prepareStatement("select distinct(hypervisor_type) from `cloud`.`cluster` where removed is null"); - rs = pstmt.executeQuery(); - while (rs.next()) { - if ("VMware".equals(rs.getString(1))) { - VMware = true; - } - } - } catch (SQLException e) { - throw new CloudRuntimeException("Error while iterating through list of hypervisors in use", e); - } - // Just update the VMware system template. Other hypervisor templates are unchanged from previous 3.0.x versions. - logger.debug("Updating VMware System Vms"); - try { - //Get 3.0.5 VMware system Vm template Id - pstmt = conn.prepareStatement("select id from `cloud`.`vm_template` where name = 'systemvm-vmware-3.0.5' and removed is null"); - rs = pstmt.executeQuery(); - if (rs.next()) { - long templateId = rs.getLong(1); - rs.close(); - pstmt.close(); - // change template type to SYSTEM - pstmt = conn.prepareStatement("update `cloud`.`vm_template` set type='SYSTEM' where id = ?"); - pstmt.setLong(1, templateId); - pstmt.executeUpdate(); - pstmt.close(); - // update template ID of system Vms - pstmt = conn.prepareStatement("update `cloud`.`vm_instance` set vm_template_id = ? where type <> 'User' and hypervisor_type = 'VMware'"); - pstmt.setLong(1, templateId); - pstmt.executeUpdate(); - pstmt.close(); - } else { - if (VMware) { - throw new CloudRuntimeException("3.0.5 VMware SystemVm Template not found. Cannot upgrade system Vms"); - } else { - logger.warn("3.0.5 VMware SystemVm Template not found. VMware hypervisor is not used, so not failing upgrade"); - } - } - } catch (SQLException e) { - throw new CloudRuntimeException("Error while updating VMware systemVM Template", e); - } - logger.debug("Updating System VM Template IDs Complete"); - } - private void addVpcProvider(Connection conn) { //Encrypt config params and change category to Hidden logger.debug("Adding VPC provider to all physical Networks in the system"); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade410to420.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade410to420.java index 94e6149e73b2..5c47087b9689 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade410to420.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade410to420.java @@ -1195,9 +1195,9 @@ private void updateGlobalDeploymentPlanner(Connection conn) { plannerName = "FirstFitPlanner"; } else if (globalValue.equals(DeploymentPlanner.AllocationAlgorithm.firstfit.toString())) { plannerName = "FirstFitPlanner"; - } else if (globalValue.equals(DeploymentPlanner.AllocationAlgorithm.userconcentratedpod_firstfit.toString())) { + } else if (globalValue.equals("userconcentratedpod_firstfit")) { plannerName = "UserConcentratedPodPlanner"; - } else if (globalValue.equals(DeploymentPlanner.AllocationAlgorithm.userconcentratedpod_random.toString())) { + } else if (globalValue.equals("userconcentratedpod_random")) { plannerName = "UserConcentratedPodPlanner"; } else if (globalValue.equals(DeploymentPlanner.AllocationAlgorithm.userdispersing.toString())) { plannerName = "UserDispersingPlanner"; @@ -1947,7 +1947,7 @@ private void migrateS3ToImageStore(Connection conn) { Map detailMap = new HashMap(); detailMap.put(ApiConstants.S3_ACCESS_KEY, s3_accesskey); - detailMap.put(ApiConstants.S3_SECRET_KEY, s3_secretkey); + detailMap.put(ApiConstants.SECRET_KEY, s3_secretkey); detailMap.put(ApiConstants.S3_BUCKET_NAME, s3_bucket); detailMap.put(ApiConstants.S3_END_POINT, s3_endpoint); detailMap.put(ApiConstants.S3_HTTPS_FLAG, String.valueOf(s3_https)); diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java index a38382623bf5..35e706595ec1 100644 --- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41810to41900.java @@ -159,7 +159,7 @@ private void modifyDateColumnNameAndCreateNewOne(Connection conn) { try (PreparedStatement pstmt = conn.prepareStatement(createNewColumn)) { pstmt.execute(); } catch (SQLException e) { - String message = String.format("Unable to crate new backups' column date due to [%s].", e.getMessage()); + String message = String.format("Unable to create new backups' column date due to [%s].", e.getMessage()); logger.error(message, e); throw new CloudRuntimeException(message, e); } diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42040to42100.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42040to42100.java new file mode 100644 index 000000000000..64ac2c90bc07 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42040to42100.java @@ -0,0 +1,220 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade.dao; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.apache.cloudstack.framework.config.ConfigKey; + +import com.cloud.upgrade.SystemVmTemplateRegistration; +import com.cloud.utils.db.TransactionLegacy; +import com.cloud.utils.exception.CloudRuntimeException; + +public class Upgrade42040to42100 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { + private SystemVmTemplateRegistration systemVmTemplateRegistration; + + @Override + public String[] getUpgradableVersionRange() { + return new String[] {"4.20.4.0", "4.21.0.0"}; + } + + @Override + public String getUpgradedVersion() { + return "4.21.0.0"; + } + + @Override + public boolean supportsRollingUpgrade() { + return false; + } + + @Override + public InputStream[] getPrepareScripts() { + final String scriptFile = "META-INF/db/schema-42040to42100.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[] {script}; + } + + @Override + public void performDataMigration(Connection conn) { + updateKubernetesClusterNodeVersions(conn); + migrateConfigurationScopeToBitmask(conn); + } + + @Override + public InputStream[] getCleanupScripts() { + final String scriptFile = "META-INF/db/schema-42040to42100-cleanup.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[] {script}; + } + + private void initSystemVmTemplateRegistration() { + systemVmTemplateRegistration = new SystemVmTemplateRegistration(""); + } + + @Override + public void updateSystemVmTemplates(Connection conn) { + logger.debug("Updating System Vm template IDs"); + initSystemVmTemplateRegistration(); + try { + systemVmTemplateRegistration.updateSystemVmTemplates(conn); + } catch (Exception e) { + throw new CloudRuntimeException("Failed to find / register SystemVM template(s)"); + } + } + + private void updateKubernetesClusterNodeVersions(Connection conn) { + //get list of all non removed kubernetes clusters + try { + Map clusterAndVersion = getKubernetesClusterIdsAndVersion(conn); + updateKubernetesNodeVersions(conn, clusterAndVersion); + } catch (Exception e) { + String errMsg = "Failed to update kubernetes cluster nodes version"; + logger.error(errMsg); + throw new CloudRuntimeException(errMsg, e); + } + } + + private Map getKubernetesClusterIdsAndVersion(Connection conn) { + String listKubernetesClusters = "SELECT c.id, v.semantic_version FROM `cloud`.`kubernetes_cluster` c JOIN `cloud`.`kubernetes_supported_version` v ON (c.kubernetes_version_id = v.id) WHERE c.removed is NULL;"; + Map clusterAndVersion = new HashMap<>(); + try { + PreparedStatement pstmt = conn.prepareStatement(listKubernetesClusters); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + clusterAndVersion.put(rs.getLong(1), rs.getString(2)); + } + rs.close(); + pstmt.close(); + } catch (SQLException e) { + String errMsg = String.format("Failed to get all the kubernetes cluster ids due to: %s", e.getMessage()); + logger.error(errMsg); + throw new CloudRuntimeException(errMsg, e); + } + return clusterAndVersion; + } + + private void updateKubernetesNodeVersions(Connection conn, Map clusterAndVersion) { + List kubernetesClusterVmIds; + for (Map.Entry clusterVersionEntry : clusterAndVersion.entrySet()) { + try { + Long cksClusterId = clusterVersionEntry.getKey(); + String cksVersion = clusterVersionEntry.getValue(); + logger.debug(String.format("Adding CKS version %s to existing CKS cluster %s nodes", cksVersion, cksClusterId)); + kubernetesClusterVmIds = getKubernetesClusterVmMapIds(conn, cksClusterId); + updateKubernetesNodeVersion(conn, kubernetesClusterVmIds, cksClusterId, cksVersion); + } catch (Exception e) { + String errMsg = String.format("Failed to update the node version for kubernetes cluster nodes for the" + + " kubernetes cluster with id: %s," + + " due to: %s", clusterVersionEntry.getKey(), e.getMessage()); + logger.error(errMsg, e); + throw new CloudRuntimeException(errMsg, e); + } + } + } + + private List getKubernetesClusterVmMapIds(Connection conn, Long cksClusterId) { + List kubernetesClusterVmIds = new ArrayList<>(); + String getKubernetesClustersVmMap = "SELECT id FROM `cloud`.`kubernetes_cluster_vm_map` WHERE cluster_id = %s;"; + try { + PreparedStatement pstmt = conn.prepareStatement(String.format(getKubernetesClustersVmMap, cksClusterId)); + ResultSet rs = pstmt.executeQuery(); + while (rs.next()) { + kubernetesClusterVmIds.add(rs.getLong(1)); + } + rs.close(); + pstmt.close(); + } catch (SQLException e) { + String errMsg = String.format("Failed to get the kubernetes cluster vm map IDs for kubernetes cluster with id: %s," + + " due to: %s", cksClusterId, e.getMessage()); + logger.error(errMsg, e); + throw new CloudRuntimeException(errMsg, e); + } + return kubernetesClusterVmIds; + } + + private void updateKubernetesNodeVersion(Connection conn, List kubernetesClusterVmIds, Long cksClusterId, String cksVersion) { + String updateKubernetesNodeVersion = "UPDATE `cloud`.`kubernetes_cluster_vm_map` set kubernetes_node_version = ? WHERE id = ?;"; + for (Long nodeVmId : kubernetesClusterVmIds) { + try { + PreparedStatement pstmt = conn.prepareStatement(updateKubernetesNodeVersion); + pstmt.setString(1, cksVersion); + pstmt.setLong(2, nodeVmId); + pstmt.executeUpdate(); + pstmt.close(); + } catch (Exception e) { + String errMsg = String.format("Failed to update the node version for kubernetes cluster nodes for the" + + " kubernetes cluster with id: %s," + + " due to: %s", cksClusterId, e.getMessage()); + logger.error(errMsg, e); + throw new CloudRuntimeException(errMsg, e); + } + } + } + + protected void migrateConfigurationScopeToBitmask(Connection conn) { + String scopeDataType = DbUpgradeUtils.getTableColumnType(conn, "configuration", "scope"); + logger.info("Data type of the column scope of table configuration is {}", scopeDataType); + if (!"varchar(255)".equals(scopeDataType)) { + return; + } + DbUpgradeUtils.addTableColumnIfNotExist(conn, "configuration", "new_scope", "BIGINT DEFAULT 0"); + migrateExistingConfigurationScopeValues(conn); + DbUpgradeUtils.dropTableColumnsIfExist(conn, "configuration", List.of("scope")); + DbUpgradeUtils.changeTableColumnIfNotExist(conn, "configuration", "new_scope", "scope", "BIGINT NOT NULL DEFAULT 0 COMMENT 'Bitmask for scope(s) of this parameter'"); + } + + protected void migrateExistingConfigurationScopeValues(Connection conn) { + StringBuilder sql = new StringBuilder("UPDATE configuration\n" + + "SET new_scope = " + + " CASE "); + for (ConfigKey.Scope scope : ConfigKey.Scope.values()) { + sql.append(" WHEN scope = '").append(scope.name()).append("' THEN ").append(scope.getBitValue()).append(" "); + } + sql.append(" ELSE 0 " + + " END " + + "WHERE scope IS NOT NULL;"); + TransactionLegacy txn = TransactionLegacy.currentTxn(); + try (PreparedStatement pstmt = txn.prepareAutoCloseStatement(sql.toString())) { + pstmt.executeUpdate(); + } catch (SQLException e) { + logger.error("Failed to migrate existing configuration scope values to bitmask", e); + throw new CloudRuntimeException(String.format("Failed to migrate existing configuration scope values to bitmask due to: %s", e.getMessage())); + } + } + + @Override + public boolean refreshPoolConnectionsAfterUpgrade() { + return true; + } +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42100to42200.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42100to42200.java new file mode 100644 index 000000000000..5138d51bb1b6 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42100to42200.java @@ -0,0 +1,103 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade.dao; + +import com.cloud.utils.exception.CloudRuntimeException; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +public class Upgrade42100to42200 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { + + @Override + public String[] getUpgradableVersionRange() { + return new String[]{"4.21.0.0", "4.22.0.0"}; + } + + @Override + public String getUpgradedVersion() { + return "4.22.0.0"; + } + + @Override + public InputStream[] getPrepareScripts() { + final String scriptFile = "META-INF/db/schema-42100to42200.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[] {script}; + } + + @Override + public void performDataMigration(Connection conn) { + updateSnapshotPolicyOwnership(conn); + updateBackupScheduleOwnership(conn); + } + + protected void updateSnapshotPolicyOwnership(Connection conn) { + // set account_id and domain_id in snapshot_policy table from volume table + String selectSql = "SELECT sp.id, v.account_id, v.domain_id FROM snapshot_policy sp, volumes v WHERE sp.volume_id = v.id AND (sp.account_id IS NULL AND sp.domain_id IS NULL)"; + String updateSql = "UPDATE snapshot_policy SET account_id = ?, domain_id = ? WHERE id = ?"; + + try (PreparedStatement selectPstmt = conn.prepareStatement(selectSql); + ResultSet rs = selectPstmt.executeQuery(); + PreparedStatement updatePstmt = conn.prepareStatement(updateSql)) { + + while (rs.next()) { + long policyId = rs.getLong(1); + long accountId = rs.getLong(2); + long domainId = rs.getLong(3); + + updatePstmt.setLong(1, accountId); + updatePstmt.setLong(2, domainId); + updatePstmt.setLong(3, policyId); + updatePstmt.executeUpdate(); + } + } catch (SQLException e) { + throw new CloudRuntimeException("Unable to update snapshot_policy table with account_id and domain_id", e); + } + } + + protected void updateBackupScheduleOwnership(Connection conn) { + // Set account_id and domain_id in backup_schedule table from vm_instance table + String selectSql = "SELECT bs.id, vm.account_id, vm.domain_id FROM backup_schedule bs, vm_instance vm WHERE bs.vm_id = vm.id AND (bs.account_id IS NULL AND bs.domain_id IS NULL)"; + String updateSql = "UPDATE backup_schedule SET account_id = ?, domain_id = ? WHERE id = ?"; + + try (PreparedStatement selectPstmt = conn.prepareStatement(selectSql); + ResultSet rs = selectPstmt.executeQuery(); + PreparedStatement updatePstmt = conn.prepareStatement(updateSql)) { + + while (rs.next()) { + long scheduleId = rs.getLong(1); + long accountId = rs.getLong(2); + long domainId = rs.getLong(3); + + updatePstmt.setLong(1, accountId); + updatePstmt.setLong(2, domainId); + updatePstmt.setLong(3, scheduleId); + updatePstmt.executeUpdate(); + } + } catch (SQLException e) { + throw new CloudRuntimeException("Unable to update backup_schedule table with account_id and domain_id", e); + } + } +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java new file mode 100644 index 000000000000..813351d75341 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42200to42210.java @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade.dao; + +import org.apache.cloudstack.vm.UnmanagedVMsManager; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.List; + +public class Upgrade42200to42210 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { + + @Override + public String[] getUpgradableVersionRange() { + return new String[] {"4.22.0.0", "4.22.1.0"}; + } + + @Override + public String getUpgradedVersion() { + return "4.22.1.0"; + } + + @Override + public void performDataMigration(Connection conn) { + removeDuplicateKVMImportTemplates(conn); + } + + private void removeDuplicateKVMImportTemplates(Connection conn) { + List templateIds = new ArrayList<>(); + try (PreparedStatement selectStmt = conn.prepareStatement(String.format("SELECT id FROM cloud.vm_template WHERE name='%s' ORDER BY id ASC", UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME))) { + ResultSet rs = selectStmt.executeQuery(); + while (rs.next()) { + templateIds.add(rs.getLong(1)); + } + + if (templateIds.size() <= 1) { + return; + } + + logger.info("Removing duplicate template " + UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME + " entries"); + Long firstTemplateId = templateIds.get(0); + + String updateTemplateSql = "UPDATE cloud.vm_instance SET vm_template_id = ? WHERE vm_template_id = ?"; + String deleteTemplateSql = "DELETE FROM cloud.vm_template WHERE id = ?"; + + try (PreparedStatement updateTemplateStmt = conn.prepareStatement(updateTemplateSql); + PreparedStatement deleteTemplateStmt = conn.prepareStatement(deleteTemplateSql)) { + for (int i = 1; i < templateIds.size(); i++) { + Long duplicateTemplateId = templateIds.get(i); + + // Update VM references + updateTemplateStmt.setLong(1, firstTemplateId); + updateTemplateStmt.setLong(2, duplicateTemplateId); + updateTemplateStmt.executeUpdate(); + + // Delete duplicate dummy template + deleteTemplateStmt.setLong(1, duplicateTemplateId); + deleteTemplateStmt.executeUpdate(); + } + } + } catch (Exception e) { + logger.warn("Failed to remove duplicate template " + UnmanagedVMsManager.KVM_VM_IMPORT_DEFAULT_TEMPLATE_NAME + " entries", e); + } + } +} diff --git a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java new file mode 100644 index 000000000000..b60a52bba88b --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade42210to42300.java @@ -0,0 +1,102 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade.dao; + +import java.io.InputStream; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +import com.cloud.utils.crypt.DBEncryptionUtil; +import com.cloud.utils.exception.CloudRuntimeException; + +public class Upgrade42210to42300 extends DbUpgradeAbstractImpl implements DbUpgrade, DbUpgradeSystemVmTemplate { + + @Override + public String[] getUpgradableVersionRange() { + return new String[]{"4.22.1.0", "4.23.0.0"}; + } + + @Override + public String getUpgradedVersion() { + return "4.23.0.0"; + } + + @Override + public InputStream[] getPrepareScripts() { + final String scriptFile = "META-INF/db/schema-42210to42300.sql"; + final InputStream script = Thread.currentThread().getContextClassLoader().getResourceAsStream(scriptFile); + if (script == null) { + throw new CloudRuntimeException("Unable to find " + scriptFile); + } + + return new InputStream[] {script}; + } + + @Override + public void performDataMigration(Connection conn) { + unhideJsInterpretationEnabled(conn); + dropUsageVmInstanceIndex(conn); + } + + protected void unhideJsInterpretationEnabled(Connection conn) { + String value = getJsInterpretationEnabled(conn); + if (value != null) { + updateJsInterpretationEnabledFields(conn, value); + } + } + + protected String getJsInterpretationEnabled(Connection conn) { + String query = "SELECT value FROM cloud.configuration WHERE name = 'js.interpretation.enabled' AND category = 'Hidden';"; + + try (PreparedStatement pstmt = conn.prepareStatement(query)) { + ResultSet rs = pstmt.executeQuery(); + if (rs.next()) { + return rs.getString("value"); + } + logger.debug("Unable to retrieve value of hidden configuration 'js.interpretation.enabled'. The configuration may already be unhidden."); + return null; + } catch (SQLException e) { + throw new CloudRuntimeException("Error while retrieving value of hidden configuration 'js.interpretation.enabled'.", e); + } + } + + protected void updateJsInterpretationEnabledFields(Connection conn, String encryptedValue) { + String query = "UPDATE cloud.configuration SET value = ?, category = 'System', component = 'JsInterpreter', is_dynamic = 1 WHERE name = 'js.interpretation.enabled';"; + + try (PreparedStatement pstmt = conn.prepareStatement(query)) { + String decryptedValue = DBEncryptionUtil.decrypt(encryptedValue); + logger.info("Updating setting 'js.interpretation.enabled' to decrypted value [{}], category 'System', component 'JsInterpreter', and is_dynamic '1'.", decryptedValue); + pstmt.setString(1, decryptedValue); + pstmt.executeUpdate(); + } catch (SQLException e) { + throw new CloudRuntimeException("Error while unhiding configuration 'js.interpretation.enabled'.", e); + } catch (CloudRuntimeException e) { + logger.warn("Error while decrypting configuration 'js.interpretation.enabled'. The configuration may already be decrypted."); + } + } + + private void dropUsageVmInstanceIndex(Connection conn) { + final List indexList = new ArrayList<>(); + logger.debug("Dropping index vm_instance_id from usage_vm_instance table if it exists"); + indexList.add("vm_instance_id"); + DbUpgradeUtils.dropKeysIfExist(conn, "cloud_usage.usage_vm_instance", indexList, false); + } +} diff --git a/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java b/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java index 96abd2d69c08..6d5315e33464 100644 --- a/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java +++ b/engine/schema/src/main/java/com/cloud/usage/UsageVolumeVO.java @@ -59,6 +59,9 @@ public class UsageVolumeVO implements InternalIdentity { @Column(name = "size") private long size; + @Column(name = "vm_id") + private Long vmId; + @Column(name = "created") @Temporal(value = TemporalType.TIMESTAMP) private Date created = null; @@ -70,13 +73,14 @@ public class UsageVolumeVO implements InternalIdentity { protected UsageVolumeVO() { } - public UsageVolumeVO(long id, long zoneId, long accountId, long domainId, Long diskOfferingId, Long templateId, long size, Date created, Date deleted) { + public UsageVolumeVO(long id, long zoneId, long accountId, long domainId, Long diskOfferingId, Long templateId, Long vmId, long size, Date created, Date deleted) { this.volumeId = id; this.zoneId = zoneId; this.accountId = accountId; this.domainId = domainId; this.diskOfferingId = diskOfferingId; this.templateId = templateId; + this.vmId = vmId; this.size = size; this.created = created; this.deleted = deleted; @@ -126,4 +130,12 @@ public void setDeleted(Date deleted) { public long getVolumeId() { return volumeId; } + + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java index 8a72182ec677..2a24016653db 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDao.java @@ -24,7 +24,7 @@ import com.cloud.utils.db.GenericDao; public interface UsageBackupDao extends GenericDao { - void updateMetrics(Long vmId, Long size, Long virtualSize); - void removeUsage(Long accountId, Long vmId, Date eventDate); + void updateMetrics(Long vmId, Long backupOfferingId, Long size, Long virtualSize); + void removeUsage(Long accountId, Long vmId, Long backupOfferingId, Date eventDate); List getUsageRecords(Long accountId, Date startDate, Date endDate); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java index 3403a8dfe5bb..3f852b0cfb5a 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageBackupDaoImpl.java @@ -36,16 +36,17 @@ @Component public class UsageBackupDaoImpl extends GenericDaoBase implements UsageBackupDao { - protected static final String UPDATE_DELETED = "UPDATE usage_backup SET removed = ? WHERE account_id = ? AND vm_id = ? and removed IS NULL"; + protected static final String UPDATE_DELETED = "UPDATE usage_backup SET removed = ? WHERE account_id = ? AND vm_id = ? and backup_offering_id = ? and removed IS NULL"; protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT id, zone_id, account_id, domain_id, vm_id, backup_offering_id, size, protected_size, created, removed FROM usage_backup WHERE " + " account_id = ? AND ((removed IS NULL AND created <= ?) OR (created BETWEEN ? AND ?) OR (removed BETWEEN ? AND ?) " + " OR ((created <= ?) AND (removed >= ?)))"; @Override - public void updateMetrics(final Long vmId, final Long size, final Long virtualSize) { + public void updateMetrics(final Long vmId, Long backupOfferingId, final Long size, final Long virtualSize) { try (TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB)) { SearchCriteria sc = this.createSearchCriteria(); sc.addAnd("vmId", SearchCriteria.Op.EQ, vmId); + sc.addAnd("backupOfferingId", SearchCriteria.Op.EQ, backupOfferingId); UsageBackupVO vo = findOneBy(sc); if (vo != null) { vo.setSize(size); @@ -58,7 +59,7 @@ public void updateMetrics(final Long vmId, final Long size, final Long virtualSi } @Override - public void removeUsage(Long accountId, Long vmId, Date eventDate) { + public void removeUsage(Long accountId, Long vmId, Long backupOfferingId, Date eventDate) { TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); try { txn.start(); @@ -67,6 +68,7 @@ public void removeUsage(Long accountId, Long vmId, Date eventDate) { pstmt.setString(1, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), eventDate)); pstmt.setLong(2, accountId); pstmt.setLong(3, vmId); + pstmt.setLong(4, backupOfferingId); pstmt.executeUpdate(); } } catch (SQLException e) { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java index 6f340501cf18..68ad8fa12edd 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageJobDaoImpl.java @@ -61,9 +61,6 @@ public long getLastJobSuccessDateMillis() { public void updateJobSuccess(Long jobId, long startMillis, long endMillis, long execTime, boolean success) { TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); try { - txn.start(); - - UsageJobVO job = lockRow(jobId, Boolean.TRUE); UsageJobVO jobForUpdate = createForUpdate(); jobForUpdate.setStartMillis(startMillis); jobForUpdate.setEndMillis(endMillis); @@ -71,11 +68,8 @@ public void updateJobSuccess(Long jobId, long startMillis, long endMillis, long jobForUpdate.setStartDate(new Date(startMillis)); jobForUpdate.setEndDate(new Date(endMillis)); jobForUpdate.setSuccess(success); - update(job.getId(), jobForUpdate); - - txn.commit(); + update(jobId, jobForUpdate); } catch (Exception ex) { - txn.rollback(); logger.error("error updating job success date", ex); throw new CloudRuntimeException(ex.getMessage()); } finally { diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java index 1da533493997..f863cd1e3a35 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageStorageDaoImpl.java @@ -57,6 +57,7 @@ public UsageStorageDaoImpl() { IdSearch.and("accountId", IdSearch.entity().getAccountId(), SearchCriteria.Op.EQ); IdSearch.and("id", IdSearch.entity().getEntityId(), SearchCriteria.Op.EQ); IdSearch.and("type", IdSearch.entity().getStorageType(), SearchCriteria.Op.EQ); + IdSearch.and("deleted", IdSearch.entity().getDeleted(), SearchCriteria.Op.NULL); IdSearch.done(); IdZoneSearch = createSearchBuilder(); @@ -74,6 +75,7 @@ public List listById(long accountId, long id, int type) { sc.setParameters("accountId", accountId); sc.setParameters("id", id); sc.setParameters("type", type); + sc.setParameters("deleted", null); return listBy(sc, null); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDao.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDao.java index 09590b739930..05287240f254 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDao.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDao.java @@ -23,9 +23,7 @@ import com.cloud.utils.db.GenericDao; public interface UsageVolumeDao extends GenericDao { - public void removeBy(long userId, long id); - - public void update(UsageVolumeVO usage); - public List getUsageRecords(Long accountId, Long domainId, Date startDate, Date endDate, boolean limit, int page); + + List listByVolumeId(long volumeId, long accountId); } diff --git a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java index 4662a6f26ce8..095070feac1c 100644 --- a/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/usage/dao/UsageVolumeDaoImpl.java @@ -18,81 +18,46 @@ import java.sql.PreparedStatement; import java.sql.ResultSet; -import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.TimeZone; -import com.cloud.exception.CloudException; +import javax.annotation.PostConstruct; + import org.springframework.stereotype.Component; import com.cloud.usage.UsageVolumeVO; import com.cloud.utils.DateUtil; import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; import com.cloud.utils.db.TransactionLegacy; @Component public class UsageVolumeDaoImpl extends GenericDaoBase implements UsageVolumeDao { - protected static final String REMOVE_BY_USERID_VOLID = "DELETE FROM usage_volume WHERE account_id = ? AND volume_id = ?"; - protected static final String UPDATE_DELETED = "UPDATE usage_volume SET deleted = ? WHERE account_id = ? AND volume_id = ? and deleted IS NULL"; - protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT volume_id, zone_id, account_id, domain_id, disk_offering_id, template_id, size, created, deleted " + protected static final String GET_USAGE_RECORDS_BY_ACCOUNT = "SELECT volume_id, zone_id, account_id, domain_id, disk_offering_id, template_id, vm_id, size, created, deleted " + "FROM usage_volume " + "WHERE account_id = ? AND ((deleted IS NULL) OR (created BETWEEN ? AND ?) OR " + " (deleted BETWEEN ? AND ?) OR ((created <= ?) AND (deleted >= ?)))"; - protected static final String GET_USAGE_RECORDS_BY_DOMAIN = "SELECT volume_id, zone_id, account_id, domain_id, disk_offering_id, template_id, size, created, deleted " + protected static final String GET_USAGE_RECORDS_BY_DOMAIN = "SELECT volume_id, zone_id, account_id, domain_id, disk_offering_id, template_id, vm_id, size, created, deleted " + "FROM usage_volume " + "WHERE domain_id = ? AND ((deleted IS NULL) OR (created BETWEEN ? AND ?) OR " + " (deleted BETWEEN ? AND ?) OR ((created <= ?) AND (deleted >= ?)))"; - protected static final String GET_ALL_USAGE_RECORDS = "SELECT volume_id, zone_id, account_id, domain_id, disk_offering_id, template_id, size, created, deleted " + protected static final String GET_ALL_USAGE_RECORDS = "SELECT volume_id, zone_id, account_id, domain_id, disk_offering_id, template_id, vm_id, size, created, deleted " + "FROM usage_volume " + "WHERE (deleted IS NULL) OR (created BETWEEN ? AND ?) OR " + " (deleted BETWEEN ? AND ?) OR ((created <= ?) AND (deleted >= ?))"; + private SearchBuilder volumeSearch; public UsageVolumeDaoImpl() { } - @Override - public void removeBy(long accountId, long volId) { - TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); - try { - txn.start(); - try(PreparedStatement pstmt = txn.prepareStatement(REMOVE_BY_USERID_VOLID);) { - if (pstmt != null) { - pstmt.setLong(1, accountId); - pstmt.setLong(2, volId); - pstmt.executeUpdate(); - } - }catch (SQLException e) { - throw new CloudException("Error removing usageVolumeVO:"+e.getMessage(), e); - } - txn.commit(); - } catch (Exception e) { - txn.rollback(); - logger.warn("Error removing usageVolumeVO:"+e.getMessage(), e); - } finally { - txn.close(); - } - } - - @Override - public void update(UsageVolumeVO usage) { - TransactionLegacy txn = TransactionLegacy.open(TransactionLegacy.USAGE_DB); - PreparedStatement pstmt = null; - try { - txn.start(); - if (usage.getDeleted() != null) { - pstmt = txn.prepareAutoCloseStatement(UPDATE_DELETED); - pstmt.setString(1, DateUtil.getDateDisplayString(TimeZone.getTimeZone("GMT"), usage.getDeleted())); - pstmt.setLong(2, usage.getAccountId()); - pstmt.setLong(3, usage.getVolumeId()); - pstmt.executeUpdate(); - } - txn.commit(); - } catch (Exception e) { - txn.rollback(); - logger.warn("Error updating UsageVolumeVO", e); - } finally { - txn.close(); - } + @PostConstruct + protected void init() { + volumeSearch = createSearchBuilder(); + volumeSearch.and("accountId", volumeSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + volumeSearch.and("volumeId", volumeSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + volumeSearch.and("deleted", volumeSearch.entity().getDeleted(), SearchCriteria.Op.NULL); + volumeSearch.done(); } @Override @@ -150,11 +115,15 @@ public List getUsageRecords(Long accountId, Long domainId, Date s if (tId == 0) { tId = null; } - long size = Long.valueOf(rs.getLong(7)); + Long vmId = Long.valueOf(rs.getLong(7)); + if (vmId == 0) { + vmId = null; + } + long size = Long.valueOf(rs.getLong(8)); Date createdDate = null; Date deletedDate = null; - String createdTS = rs.getString(8); - String deletedTS = rs.getString(9); + String createdTS = rs.getString(9); + String deletedTS = rs.getString(10); if (createdTS != null) { createdDate = DateUtil.parseDateString(s_gmtTimeZone, createdTS); @@ -163,7 +132,7 @@ public List getUsageRecords(Long accountId, Long domainId, Date s deletedDate = DateUtil.parseDateString(s_gmtTimeZone, deletedTS); } - usageRecords.add(new UsageVolumeVO(vId, zoneId, acctId, dId, doId, tId, size, createdDate, deletedDate)); + usageRecords.add(new UsageVolumeVO(vId, zoneId, acctId, dId, doId, tId, vmId, size, createdDate, deletedDate)); } } catch (Exception e) { txn.rollback(); @@ -174,4 +143,13 @@ public List getUsageRecords(Long accountId, Long domainId, Date s return usageRecords; } + + @Override + public List listByVolumeId(long volumeId, long accountId) { + SearchCriteria sc = volumeSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParameters("volumeId", volumeId); + sc.setParameters("deleted", null); + return listBy(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java index 535b5eae390f..cbacf9af5721 100644 --- a/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/AccountDetailsDaoImpl.java @@ -33,6 +33,7 @@ import com.cloud.domain.dao.DomainDao; import com.cloud.domain.dao.DomainDetailsDao; import com.cloud.user.dao.AccountDao; +import com.cloud.utils.Pair; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -156,4 +157,13 @@ public String getConfigValue(long id, String key) { } return value; } + + @Override + public Pair getParentScope(long id) { + Account account = _accountDao.findById(id); + if (account == null) { + return null; + } + return new Pair<>(getScope().getParent(), account.getDomainId()); + } } diff --git a/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java b/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java index e4fcbad6b02f..7345eeb48539 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserAccountVO.java @@ -36,7 +36,6 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import org.apache.commons.lang3.StringUtils; -import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.GenericDao; @Entity @@ -69,13 +68,6 @@ public class UserAccountVO implements UserAccount, InternalIdentity { @Column(name = "state") private String state; - @Column(name = "api_key") - private String apiKey = null; - - @Encrypt - @Column(name = "secret_key") - private String secretKey = null; - @Column(name = GenericDao.CREATED_COLUMN) private Date created; @@ -203,33 +195,11 @@ public void setState(String state) { this.state = state; } - @Override - public String getApiKey() { - return apiKey; - } - - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - @Override - public String getSecretKey() { - return secretKey; - } - - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - @Override public Date getCreated() { return created; } -// public void setCreated(Date created) { -// this.created = created; -// } - @Override public Date getRemoved() { return removed; diff --git a/engine/schema/src/main/java/com/cloud/user/UserDataVO.java b/engine/schema/src/main/java/com/cloud/user/UserDataVO.java index a8e48ad22b1a..e8864976069d 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserDataVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserDataVO.java @@ -65,6 +65,9 @@ public UserDataVO() { @Column(name = GenericDao.REMOVED_COLUMN) private Date removed; + @Column(name = "for_cks") + private boolean forCks; + @Override public long getDomainId() { return domainId; @@ -105,6 +108,11 @@ public String getParams() { return params; } + @Override + public boolean isForCks() { + return forCks; + } + public void setAccountId(long accountId) { this.accountId = accountId; } @@ -132,4 +140,6 @@ public void setRemoved(Date removed) { public Date getRemoved() { return removed; } + + public void setForCks(boolean forCks) { this.forCks = forCks; } } diff --git a/engine/schema/src/main/java/com/cloud/user/UserVO.java b/engine/schema/src/main/java/com/cloud/user/UserVO.java index d74aa7ed41b8..1b89bc215cf7 100644 --- a/engine/schema/src/main/java/com/cloud/user/UserVO.java +++ b/engine/schema/src/main/java/com/cloud/user/UserVO.java @@ -33,7 +33,6 @@ import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import com.cloud.user.Account.State; -import com.cloud.utils.db.Encrypt; import com.cloud.utils.db.GenericDao; import org.apache.commons.lang3.StringUtils; @@ -71,13 +70,6 @@ public class UserVO implements User, Identity, InternalIdentity { @Enumerated(value = EnumType.STRING) private State state; - @Column(name = "api_key") - private String apiKey = null; - - @Encrypt - @Column(name = "secret_key") - private String secretKey = null; - @Column(name = GenericDao.CREATED_COLUMN) private Date created; @@ -150,8 +142,6 @@ public UserVO(UserVO user) { this.setTimezone(user.getTimezone()); this.setUuid(user.getUuid()); this.setSource(user.getSource()); - this.setApiKey(user.getApiKey()); - this.setSecretKey(user.getSecretKey()); this.setExternalEntity(user.getExternalEntity()); this.setRegistered(user.isRegistered()); this.setRegistrationToken(user.getRegistrationToken()); @@ -243,26 +233,6 @@ public void setState(State state) { this.state = state; } - @Override - public String getApiKey() { - return apiKey; - } - - @Override - public void setApiKey(String apiKey) { - this.apiKey = apiKey; - } - - @Override - public String getSecretKey() { - return secretKey; - } - - @Override - public void setSecretKey(String secretKey) { - this.secretKey = secretKey; - } - @Override public String getTimezone() { if (StringUtils.isEmpty(timezone)) { diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDao.java b/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDao.java index de3b769571e9..e377bbab94ed 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDao.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDao.java @@ -30,6 +30,4 @@ public interface UserAccountDao extends GenericDao { List getUserAccountByEmail(String email, Long domainId); boolean validateUsernameInDomain(String username, Long domainId); - - UserAccount getUserByApiKey(String apiKey); } diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDaoImpl.java index c9de9a367eed..28392abbff5c 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserAccountDaoImpl.java @@ -19,7 +19,6 @@ import com.cloud.user.UserAccount; import com.cloud.user.UserAccountVO; import com.cloud.utils.db.GenericDaoBase; -import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; import org.springframework.stereotype.Component; @@ -28,14 +27,6 @@ @Component public class UserAccountDaoImpl extends GenericDaoBase implements UserAccountDao { - protected final SearchBuilder userAccountSearch; - - public UserAccountDaoImpl() { - userAccountSearch = createSearchBuilder(); - userAccountSearch.and("apiKey", userAccountSearch.entity().getApiKey(), SearchCriteria.Op.EQ); - userAccountSearch.done(); - } - @Override public List getAllUsersByNameAndEntity(String username, String entity) { if (username == null) { @@ -79,12 +70,4 @@ public boolean validateUsernameInDomain(String username, Long domainId) { } return false; } - - @Override - public UserAccount getUserByApiKey(String apiKey) { - SearchCriteria sc = userAccountSearch.create(); - sc.setParameters("apiKey", apiKey); - return findOneBy(sc); - } - } diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java b/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java index 14b074251508..2e160efb9506 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserDao.java @@ -37,13 +37,6 @@ public interface UserDao extends GenericDao { List listByAccount(long accountId); - /** - * Finds a user based on the secret key provided. - * @param secretKey - * @return - */ - UserVO findUserBySecretKey(String secretKey); - /** * Finds a user based on the registration token provided. * @param registrationToken diff --git a/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java b/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java index 8baf732c2406..de60e48dff8f 100644 --- a/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/user/dao/UserDaoImpl.java @@ -65,10 +65,6 @@ protected UserDaoImpl() { UserIdSearch.and("id", UserIdSearch.entity().getId(), SearchCriteria.Op.EQ); UserIdSearch.done(); - SecretKeySearch = createSearchBuilder(); - SecretKeySearch.and("secretKey", SecretKeySearch.entity().getSecretKey(), SearchCriteria.Op.EQ); - SecretKeySearch.done(); - RegistrationTokenSearch = createSearchBuilder(); RegistrationTokenSearch.and("registrationToken", RegistrationTokenSearch.entity().getRegistrationToken(), SearchCriteria.Op.EQ); RegistrationTokenSearch.done(); @@ -121,13 +117,6 @@ public List findUsersLike(String username) { return listBy(sc); } - @Override - public UserVO findUserBySecretKey(String secretKey) { - SearchCriteria sc = SecretKeySearch.create(); - sc.setParameters("secretKey", secretKey); - return findOneBy(sc); - } - @Override public UserVO findUserByRegistrationToken(String registrationToken) { SearchCriteria sc = RegistrationTokenSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java index 81a11241e4be..d8f2838dd477 100644 --- a/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/ConsoleSessionVO.java @@ -19,6 +19,8 @@ package com.cloud.vm; +import org.apache.cloudstack.consoleproxy.ConsoleSession; + import java.util.Date; import javax.persistence.Column; @@ -32,7 +34,7 @@ @Entity @Table(name = "console_session") -public class ConsoleSessionVO { +public class ConsoleSessionVO implements ConsoleSession { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -45,6 +47,9 @@ public class ConsoleSessionVO { @Column(name = "created") private Date created; + @Column(name = "domain_id") + private long domainId; + @Column(name = "account_id") private long accountId; @@ -64,6 +69,12 @@ public class ConsoleSessionVO { @Column(name = "removed") private Date removed; + @Column(name = "console_endpoint_creator_address") + private String consoleEndpointCreatorAddress; + + @Column(name = "client_address") + private String clientAddress; + public long getId() { return id; } @@ -80,6 +91,7 @@ public void setUuid(String uuid) { this.uuid = uuid; } + @Override public Date getCreated() { return created; } @@ -88,6 +100,16 @@ public void setCreated(Date created) { this.created = created; } + @Override + public long getDomainId() { + return domainId; + } + + public void setDomainId(long domainId) { + this.domainId = domainId; + } + + @Override public long getAccountId() { return accountId; } @@ -96,6 +118,7 @@ public void setAccountId(long accountId) { this.accountId = accountId; } + @Override public long getUserId() { return userId; } @@ -104,6 +127,7 @@ public void setUserId(long userId) { this.userId = userId; } + @Override public long getInstanceId() { return instanceId; } @@ -112,6 +136,7 @@ public void setInstanceId(long instanceId) { this.instanceId = instanceId; } + @Override public long getHostId() { return hostId; } @@ -120,6 +145,7 @@ public void setHostId(long hostId) { this.hostId = hostId; } + @Override public Date getRemoved() { return removed; } @@ -128,6 +154,7 @@ public void setRemoved(Date removed) { this.removed = removed; } + @Override public Date getAcquired() { return acquired; } @@ -135,4 +162,22 @@ public Date getAcquired() { public void setAcquired(Date acquired) { this.acquired = acquired; } + + @Override + public String getConsoleEndpointCreatorAddress() { + return consoleEndpointCreatorAddress; + } + + public void setConsoleEndpointCreatorAddress(String consoleEndpointCreatorAddress) { + this.consoleEndpointCreatorAddress = consoleEndpointCreatorAddress; + } + + @Override + public String getClientAddress() { + return clientAddress; + } + + public void setClientAddress(String clientAddress) { + this.clientAddress = clientAddress; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java b/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java new file mode 100644 index 000000000000..9a8a769f0a56 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/ImportVMTaskVO.java @@ -0,0 +1,270 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.vm; + +import org.apache.cloudstack.vm.ImportVmTask; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "import_vm_task") +public class ImportVMTaskVO implements ImportVmTask { + + public ImportVMTaskVO(long zoneId, long accountId, long userId, String displayName, + String vcenter, String datacenter, String sourceVMName, long convertHostId, long importHostId) { + this.zoneId = zoneId; + this.accountId = accountId; + this.userId = userId; + this.displayName = displayName; + this.vcenter = vcenter; + this.datacenter = datacenter; + this.sourceVMName = sourceVMName; + this.step = Step.Prepare; + this.uuid = UUID.randomUUID().toString(); + this.convertHostId = convertHostId; + this.importHostId = importHostId; + } + + public ImportVMTaskVO() { + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "zone_id") + private long zoneId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "user_id") + private long userId; + + @Column(name = "vm_id") + private Long vmId; + @Column(name = "display_name") + private String displayName; + + @Column(name = "vcenter") + private String vcenter; + + @Column(name = "datacenter") + private String datacenter; + + @Column(name = "source_vm_name") + private String sourceVMName; + + @Column(name = "convert_host_id") + private long convertHostId; + + @Column(name = "import_host_id") + private long importHostId; + + @Column(name = "step") + private Step step; + + @Column(name = "state") + private TaskState state; + + @Column(name = "description") + private String description; + + @Column(name = "duration") + private Long duration; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = "updated") + @Temporal(value = TemporalType.TIMESTAMP) + private Date updated; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + @Override + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public long getZoneId() { + return zoneId; + } + + public void setZoneId(long zoneId) { + this.zoneId = zoneId; + } + + public long getAccountId() { + return accountId; + } + + public void setAccountId(long accountId) { + this.accountId = accountId; + } + + public long getUserId() { + return userId; + } + + public void setUserId(long userId) { + this.userId = userId; + } + + public Long getVmId() { + return vmId; + } + + public void setVmId(Long vmId) { + this.vmId = vmId; + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public String getVcenter() { + return vcenter; + } + + public void setVcenter(String vcenter) { + this.vcenter = vcenter; + } + + public String getDatacenter() { + return datacenter; + } + + public void setDatacenter(String datacenter) { + this.datacenter = datacenter; + } + + public String getSourceVMName() { + return sourceVMName; + } + + public void setSourceVMName(String sourceVMName) { + this.sourceVMName = sourceVMName; + } + + public long getConvertHostId() { + return convertHostId; + } + + public void setConvertHostId(long convertHostId) { + this.convertHostId = convertHostId; + } + + public long getImportHostId() { + return importHostId; + } + + public void setImportHostId(long importHostId) { + this.importHostId = importHostId; + } + + public Step getStep() { + return step; + } + + public void setStep(Step step) { + this.step = step; + } + + public TaskState getState() { + return state; + } + + public void setState(TaskState state) { + this.state = state; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public Long getDuration() { + return duration; + } + + public void setDuration(Long duration) { + this.duration = duration; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/NicVO.java b/engine/schema/src/main/java/com/cloud/vm/NicVO.java index 6c569e22dd95..65946b8d8210 100644 --- a/engine/schema/src/main/java/com/cloud/vm/NicVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/NicVO.java @@ -131,6 +131,9 @@ protected NicVO() { @Column(name = "mtu") Integer mtu; + @Column(name = "enabled") + boolean enabled; + @Transient transient String nsxLogicalSwitchUuid; @@ -143,6 +146,7 @@ public NicVO(String reserver, Long instanceId, long configurationId, VirtualMach this.networkId = configurationId; this.state = State.Allocated; this.vmType = vmType; + this.enabled = true; } @Override @@ -397,6 +401,14 @@ public void setNsxLogicalSwitchPortUuid(String nsxLogicalSwitchPortUuid) { this.nsxLogicalSwitchPortUuid = nsxLogicalSwitchPortUuid; } + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + @Override public int hashCode() { return new HashCodeBuilder(17, 31).append(id).toHashCode(); diff --git a/engine/schema/src/main/java/com/cloud/vm/UserVmDetailVO.java b/engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java similarity index 90% rename from engine/schema/src/main/java/com/cloud/vm/UserVmDetailVO.java rename to engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java index 81bb6dd9d4f3..7879aa24556b 100755 --- a/engine/schema/src/main/java/com/cloud/vm/UserVmDetailVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/VMInstanceDetailVO.java @@ -26,8 +26,8 @@ import org.apache.cloudstack.api.ResourceDetail; @Entity -@Table(name = "user_vm_details") -public class UserVmDetailVO implements ResourceDetail { +@Table(name = "vm_instance_details") +public class VMInstanceDetailVO implements ResourceDetail { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @@ -45,10 +45,10 @@ public class UserVmDetailVO implements ResourceDetail { @Column(name = "display") private boolean display = true; - public UserVmDetailVO() { + public VMInstanceDetailVO() { } - public UserVmDetailVO(long vmId, String name, String value, boolean display) { + public VMInstanceDetailVO(long vmId, String name, String value, boolean display) { this.resourceId = vmId; this.name = name; this.value = value; diff --git a/engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java b/engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java new file mode 100644 index 000000000000..f4a3f1168188 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/VmIsoMapVO.java @@ -0,0 +1,83 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "vm_iso_map") +public class VmIsoMapVO implements InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "vm_id") + private long vmId; + + @Column(name = "iso_id") + private long isoId; + + @Column(name = "device_seq") + private int deviceSeq; + + @Column(name = "created") + @Temporal(TemporalType.TIMESTAMP) + private Date created; + + public VmIsoMapVO() { + } + + public VmIsoMapVO(long vmId, long isoId, int deviceSeq) { + this.vmId = vmId; + this.isoId = isoId; + this.deviceSeq = deviceSeq; + this.created = new Date(); + } + + @Override + public long getId() { + return id; + } + + public long getVmId() { + return vmId; + } + + public long getIsoId() { + return isoId; + } + + public int getDeviceSeq() { + return deviceSeq; + } + + public Date getCreated() { + return created; + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java index 79158dd13b26..b8fb9557a356 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDao.java @@ -19,6 +19,7 @@ package com.cloud.vm.dao; +import com.cloud.utils.Pair; import com.cloud.vm.ConsoleSessionVO; import com.cloud.utils.db.GenericDao; @@ -33,7 +34,12 @@ public interface ConsoleSessionDao extends GenericDao { int expungeSessionsOlderThanDate(Date date); - void acquireSession(String sessionUuid); + void acquireSession(String sessionUuid, String clientAddress); int expungeByVmList(List vmIds, Long batchSize); + + Pair, Integer> listConsoleSessions(Long id, List domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java index 487096744512..562142eecc81 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ConsoleSessionDaoImpl.java @@ -22,6 +22,8 @@ import java.util.Date; import java.util.List; +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; import org.apache.commons.collections.CollectionUtils; import com.cloud.utils.db.GenericDaoBase; @@ -30,13 +32,28 @@ import com.cloud.vm.ConsoleSessionVO; public class ConsoleSessionDaoImpl extends GenericDaoBase implements ConsoleSessionDao { + private static final String ID = "id"; + private static final String DOMAIN_IDS = "domainIds"; + private static final String ACCOUNT_ID = "accountId"; + private static final String USER_ID = "userId"; + private static final String HOST_ID = "hostId"; + private static final String INSTANCE_ID = "instanceId"; + private static final String VM_IDS = "vmIds"; + private static final String START_DATE = "startDate"; + private static final String END_DATE = "endDate"; + private static final String CREATOR_ADDRESS = "creatorAddress"; + private static final String CLIENT_ADDRESS = "clientAddress"; + private static final String ACQUIRED = "acquired"; + private static final String CREATED = "created"; + private static final String REMOVED = "removed"; + private static final String REMOVED_NOT_NULL = "removedNotNull"; private final SearchBuilder searchByRemovedDate; public ConsoleSessionDaoImpl() { searchByRemovedDate = createSearchBuilder(); - searchByRemovedDate.and("removedNotNull", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL); - searchByRemovedDate.and("removed", searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ); + searchByRemovedDate.and(REMOVED_NOT_NULL, searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.NNULL); + searchByRemovedDate.and(REMOVED, searchByRemovedDate.entity().getRemoved(), SearchCriteria.Op.LTEQ); } @Override @@ -57,14 +74,15 @@ public boolean isSessionAllowed(String sessionUuid) { @Override public int expungeSessionsOlderThanDate(Date date) { SearchCriteria searchCriteria = searchByRemovedDate.create(); - searchCriteria.setParameters("removed", date); + searchCriteria.setParameters(REMOVED, date); return expunge(searchCriteria); } @Override - public void acquireSession(String sessionUuid) { + public void acquireSession(String sessionUuid, String clientAddress) { ConsoleSessionVO consoleSessionVO = findByUuid(sessionUuid); consoleSessionVO.setAcquired(new Date()); + consoleSessionVO.setClientAddress(clientAddress); update(consoleSessionVO.getId(), consoleSessionVO); } @@ -74,9 +92,66 @@ public int expungeByVmList(List vmIds, Long batchSize) { return 0; } SearchBuilder sb = createSearchBuilder(); - sb.and("vmIds", sb.entity().getInstanceId(), SearchCriteria.Op.IN); + sb.and(VM_IDS, sb.entity().getInstanceId(), SearchCriteria.Op.IN); SearchCriteria sc = sb.create(); - sc.setParameters("vmIds", vmIds.toArray()); + sc.setParameters(VM_IDS, vmIds.toArray()); return batchExpunge(sc, batchSize); } + + @Override + public Pair, Integer> listConsoleSessions(Long id, List domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, boolean acquired, Long pageSizeVal, Long startIndex) { + Filter filter = new Filter(ConsoleSessionVO.class, CREATED, false, startIndex, pageSizeVal); + SearchCriteria searchCriteria = createListConsoleSessionsSearchCriteria(id, domainIds, accountId, userId, hostId, + startDate, endDate, instanceId, consoleEndpointCreatorAddress, clientAddress, activeOnly, acquired); + + return searchAndCount(searchCriteria, filter, true); + } + + private SearchCriteria createListConsoleSessionsSearchCriteria(Long id, List domainIds, Long accountId, Long userId, Long hostId, + Date startDate, Date endDate, Long instanceId, + String consoleEndpointCreatorAddress, String clientAddress, + boolean activeOnly, boolean acquired) { + SearchCriteria searchCriteria = createListConsoleSessionsSearchBuilder(activeOnly, acquired).create(); + + searchCriteria.setParametersIfNotNull(ID, id); + searchCriteria.setParametersIfNotNull(DOMAIN_IDS, domainIds.toArray()); + searchCriteria.setParametersIfNotNull(ACCOUNT_ID, accountId); + searchCriteria.setParametersIfNotNull(USER_ID, userId); + searchCriteria.setParametersIfNotNull(HOST_ID, hostId); + searchCriteria.setParametersIfNotNull(INSTANCE_ID, instanceId); + searchCriteria.setParametersIfNotNull(START_DATE, startDate); + searchCriteria.setParametersIfNotNull(END_DATE, endDate); + searchCriteria.setParametersIfNotNull(CREATOR_ADDRESS, consoleEndpointCreatorAddress); + searchCriteria.setParametersIfNotNull(CLIENT_ADDRESS, clientAddress); + + return searchCriteria; + } + + private SearchBuilder createListConsoleSessionsSearchBuilder(boolean activeOnly, boolean acquired) { + SearchBuilder searchBuilder = createSearchBuilder(); + + searchBuilder.and(ID, searchBuilder.entity().getId(), SearchCriteria.Op.EQ); + searchBuilder.and(DOMAIN_IDS, searchBuilder.entity().getDomainId(), SearchCriteria.Op.IN); + searchBuilder.and(ACCOUNT_ID, searchBuilder.entity().getAccountId(), SearchCriteria.Op.EQ); + searchBuilder.and(USER_ID, searchBuilder.entity().getUserId(), SearchCriteria.Op.EQ); + searchBuilder.and(HOST_ID, searchBuilder.entity().getHostId(), SearchCriteria.Op.EQ); + searchBuilder.and(INSTANCE_ID, searchBuilder.entity().getInstanceId(), SearchCriteria.Op.EQ); + searchBuilder.and(START_DATE, searchBuilder.entity().getCreated(), SearchCriteria.Op.GTEQ); + searchBuilder.and(END_DATE, searchBuilder.entity().getCreated(), SearchCriteria.Op.LTEQ); + searchBuilder.and(CREATOR_ADDRESS, searchBuilder.entity().getConsoleEndpointCreatorAddress(), SearchCriteria.Op.EQ); + searchBuilder.and(CLIENT_ADDRESS, searchBuilder.entity().getClientAddress(), SearchCriteria.Op.EQ); + + if (activeOnly) { + searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); + searchBuilder.and(REMOVED, searchBuilder.entity().getRemoved(), SearchCriteria.Op.NULL); + } else if (acquired) { + searchBuilder.and(ACQUIRED, searchBuilder.entity().getAcquired(), SearchCriteria.Op.NNULL); + } + + searchBuilder.done(); + return searchBuilder; + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDao.java new file mode 100644 index 000000000000..a4f0a155da41 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDao.java @@ -0,0 +1,31 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// +package com.cloud.vm.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.ImportVMTaskVO; +import org.apache.cloudstack.vm.ImportVmTask; +import java.util.List; + +public interface ImportVMTaskDao extends GenericDao { + + Pair, Integer> listImportVMTasks(Long zoneId, Long accountId, String vcenter, Long convertHostId, + ImportVmTask.TaskState state, Long startIndex, Long pageSizeVal); +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java new file mode 100644 index 000000000000..da9c391af9db --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/ImportVMTaskDaoImpl.java @@ -0,0 +1,74 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.ImportVMTaskVO; +import org.apache.cloudstack.vm.ImportVmTask; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.util.List; + +@Component +public class ImportVMTaskDaoImpl extends GenericDaoBase implements ImportVMTaskDao { + + private SearchBuilder AllFieldsSearch; + + public ImportVMTaskDaoImpl() { + } + + @PostConstruct + void init() { + AllFieldsSearch = createSearchBuilder(); + AllFieldsSearch.and("zoneId", AllFieldsSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("accountId", AllFieldsSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("vcenter", AllFieldsSearch.entity().getVcenter(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("convertHostId", AllFieldsSearch.entity().getConvertHostId(), SearchCriteria.Op.EQ); + AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), SearchCriteria.Op.EQ); + AllFieldsSearch.done(); + } + + + @Override + public Pair, Integer> listImportVMTasks(Long zoneId, Long accountId, String vcenter, Long convertHostId, + ImportVmTask.TaskState state, Long startIndex, Long pageSizeVal) { + SearchCriteria sc = AllFieldsSearch.create(); + if (zoneId != null) { + sc.setParameters("zoneId", zoneId); + } + if (accountId != null) { + sc.setParameters("accountId", accountId); + } + if (StringUtils.isNotBlank(vcenter)) { + sc.setParameters("vcenter", vcenter); + } + if (convertHostId != null) { + sc.setParameters("convertHostId", convertHostId); + } + if (state != null) { + sc.setParameters("state", state); + } + Filter filter = new Filter(ImportVMTaskVO.class, "created", false, startIndex, pageSizeVal); + return searchAndCount(sc, filter); + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java index d2798eae8f7b..ad599053ff0c 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDao.java @@ -26,6 +26,8 @@ public interface NicDao extends GenericDao { List listByVmId(long instanceId); + int countByVmId(long instanceId); + List listByVmIdOrderByDeviceId(long instanceId); List listIpAddressInNetwork(long networkConfigId); @@ -46,8 +48,12 @@ public interface NicDao extends GenericDao { NicVO findByNetworkIdAndTypeIncludingRemoved(long networkId, VirtualMachine.Type vmType); + NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType); + NicVO findByIp4AddressAndNetworkId(String ip4Address, long networkId); + NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId); + NicVO findByNetworkIdAndMacAddress(long networkId, String mac); NicVO findDefaultNicForVM(long instanceId); @@ -93,6 +99,8 @@ public interface NicDao extends GenericDao { NicVO findByMacAddress(String macAddress, long networkId); + List listByMacAddresses(List macAddresses); + NicVO findByNetworkIdAndMacAddressIncludingRemoved(long networkId, String mac); List findNicsByIpv6GatewayIpv6CidrAndReserver(String ipv6Gateway, String ipv6Cidr, String reserverName); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java index ee8f4293e062..e468b1b9a3bf 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDaoImpl.java @@ -18,12 +18,13 @@ import java.net.URI; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import javax.annotation.PostConstruct; import javax.inject.Inject; -import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.collections4.CollectionUtils; import org.springframework.stereotype.Component; import com.cloud.utils.db.Filter; @@ -69,8 +70,9 @@ protected void init() { AllFieldsSearch.and("secondaryip", AllFieldsSearch.entity().getSecondaryIp(), Op.EQ); AllFieldsSearch.and("nicid", AllFieldsSearch.entity().getId(), Op.EQ); AllFieldsSearch.and("strategy", AllFieldsSearch.entity().getReservationStrategy(), Op.EQ); + AllFieldsSearch.and("strategyNEQ", AllFieldsSearch.entity().getReservationStrategy(), Op.NEQ); AllFieldsSearch.and("reserverName",AllFieldsSearch.entity().getReserver(),Op.EQ); - AllFieldsSearch.and("macAddress", AllFieldsSearch.entity().getMacAddress(), Op.EQ); + AllFieldsSearch.and("macAddress", AllFieldsSearch.entity().getMacAddress(), Op.IN); AllFieldsSearch.and("deviceid", AllFieldsSearch.entity().getDeviceId(), Op.EQ); AllFieldsSearch.and("ipv6Gateway", AllFieldsSearch.entity().getIPv6Gateway(), Op.EQ); AllFieldsSearch.and("ipv6Cidr", AllFieldsSearch.entity().getIPv6Cidr(), Op.EQ); @@ -125,6 +127,13 @@ public List listByVmId(long instanceId) { return listBy(sc); } + @Override + public int countByVmId(long instanceId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("instance", instanceId); + return getCount(sc); + } + @Override public List listByVmIdOrderByDeviceId(long instanceId) { SearchCriteria sc = AllFieldsSearch.create(); @@ -195,6 +204,15 @@ public NicVO findByNetworkIdAndTypeIncludingRemoved(long networkId, VirtualMachi return findByNetworkIdAndTypeInternal(networkId, vmType, true); } + @Override + public NicVO findNonPlaceHolderByNetworkIdAndType(long networkId, VirtualMachine.Type vmType) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("network", networkId); + sc.setParameters("vmType", vmType); + sc.setParameters("strategyNEQ", Nic.ReservationStrategy.PlaceHolder.toString()); + return findOneBy(sc); + } + @Override public NicVO findByNetworkIdTypeAndGateway(long networkId, VirtualMachine.Type vmType, String gateway) { SearchCriteria sc = AllFieldsSearch.create(); @@ -222,6 +240,16 @@ public NicVO findByIp4AddressAndNetworkId(String ip4Address, long networkId) { return findOneBy(sc); } + @Override + public NicVO findNonPlaceHolderByIp4AddressAndNetworkId(String ip4Address, long networkId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("address", ip4Address); + sc.setParameters("network", networkId); + sc.setParameters("strategyNEQ", Nic.ReservationStrategy.PlaceHolder.toString()); + return findOneBy(sc); + } + + @Override public NicVO findByNetworkIdAndMacAddress(long networkId, String mac) { SearchCriteria sc = AllFieldsSearch.create(); @@ -407,6 +435,16 @@ public NicVO findByMacAddress(String macAddress, long networkId) { return findOneBy(sc); } + @Override + public List listByMacAddresses(List macAddresses) { + if (CollectionUtils.isEmpty(macAddresses)) { + return Collections.emptyList(); + } + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("macAddress", macAddresses.toArray()); + return listBy(sc); + } + @Override public List findNicsByIpv6GatewayIpv6CidrAndReserver(String ipv6Gateway, String ipv6Cidr, String reserverName) { SearchCriteria sc = AllFieldsSearch.create(); diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDao.java index 2ca901fa3e55..403727ed9c61 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDao.java @@ -16,10 +16,14 @@ // under the License. package com.cloud.vm.dao; +import java.util.Set; + import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; import com.cloud.utils.db.GenericDao; import com.cloud.vm.NicDetailVO; public interface NicDetailsDao extends GenericDao, ResourceDetailsDao { + + void removeDetailsForNicIds(String resourceName, Set nicIds); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDaoImpl.java index 2a0b02aae1d9..1011f0588bbc 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicDetailsDaoImpl.java @@ -17,17 +17,43 @@ package com.cloud.vm.dao; -import org.springframework.stereotype.Component; +import java.util.Set; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; import com.cloud.vm.NicDetailVO; @Component public class NicDetailsDaoImpl extends ResourceDetailsDaoBase implements NicDetailsDao { + private final SearchBuilder ResourceIdNameSearch; + + public NicDetailsDaoImpl() { + super(); + ResourceIdNameSearch = createSearchBuilder(); + ResourceIdNameSearch.and(ApiConstants.NAME, ResourceIdNameSearch.entity().getName(), SearchCriteria.Op.EQ); + ResourceIdNameSearch.and(ApiConstants.RESOURCE_ID, ResourceIdNameSearch.entity().getResourceId(), SearchCriteria.Op.IN); + ResourceIdNameSearch.done(); + } + @Override public void addDetail(long resourceId, String key, String value, boolean display) { super.addDetail(new NicDetailVO(resourceId, key, value, display)); } + + @Override + public void removeDetailsForNicIds(String resourceName, Set nicIds) { + if (CollectionUtils.isEmpty(nicIds)) { + return; + } + SearchCriteria sc = ResourceIdNameSearch.create(); + sc.setParameters(ApiConstants.NAME, resourceName); + sc.setParameters(ApiConstants.RESOURCE_ID, nicIds.toArray()); + remove(sc); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java index 4c8208b4be84..baa331d81f67 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/NicSecondaryIpVO.java @@ -43,7 +43,7 @@ public NicSecondaryIpVO(long nicId, String ipaddr, long vmId, long accountId, lo this.networkId = networkId; } - public NicSecondaryIpVO(long nicId, String ip4Address, String ip6Address, long vmId, long accountId, long domainId, long networkId) { + public NicSecondaryIpVO(long nicId, String ip4Address, String ip6Address, long vmId, long accountId, long domainId, long networkId, String description) { this.nicId = nicId; this.vmId = vmId; this.ip4Address = ip4Address; @@ -51,6 +51,7 @@ public NicSecondaryIpVO(long nicId, String ip4Address, String ip6Address, long v this.accountId = accountId; this.domainId = domainId; this.networkId = networkId; + this.description = description; } protected NicSecondaryIpVO() { @@ -88,6 +89,18 @@ protected NicSecondaryIpVO() { @Column(name = "vmId") long vmId; + @Column(name = "description") + String description; + + @Override + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + @Override public String toString() { return String.format("NicSecondaryIp %s", diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java index dac55a4ccc38..761053a89f0c 100644 --- a/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/UserVmDaoImpl.java @@ -57,7 +57,7 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.NicVO; -import com.cloud.vm.UserVmDetailVO; +import com.cloud.vm.VMInstanceDetailVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.VirtualMachine; import com.cloud.vm.VirtualMachine.State; @@ -124,13 +124,13 @@ public class UserVmDaoImpl extends GenericDaoBase implements Use + "left join security_group on security_group_vm_map.security_group_id=security_group.id " + "left join nics on vm_instance.id=nics.instance_id " + "left join networks on nics.network_id=networks.id " + "left join user_ip_address on user_ip_address.vm_id=vm_instance.id " + "where vm_instance.id in ("; - private static final String VMS_DETAIL_BY_NAME = "select vm_instance.instance_name, vm_instance.vm_type, vm_instance.id , user_vm_details.value, user_vm_details.name from vm_instance " - + "left join user_vm_details on vm_instance.id = user_vm_details.vm_id where (user_vm_details.name is null or user_vm_details.name = ? ) and vm_instance.instance_name in ("; + private static final String VMS_DETAIL_BY_NAME = "select vm_instance.instance_name, vm_instance.vm_type, vm_instance.id , vm_instance_details.value, vm_instance_details.name from vm_instance " + + "left join vm_instance_details on vm_instance.id = vm_instance_details.vm_id where (vm_instance_details.name is null or vm_instance_details.name = ? ) and vm_instance.instance_name in ("; private static final int VM_DETAILS_BATCH_SIZE = 100; @Inject - protected UserVmDetailsDao _detailsDao; + protected VMInstanceDetailsDao _detailsDao; @Inject protected NicDao _nicDao; @@ -445,10 +445,10 @@ public void saveDetails(UserVmVO vm, List hiddenDetails) { final Map visibilityMap = _detailsDao.listDetailsVisibility(vm.getId()); - List details = new ArrayList(); + List details = new ArrayList(); for (Map.Entry entry : detailsStr.entrySet()) { boolean display = !hiddenDetails.contains(entry.getKey()) && visibilityMap.getOrDefault(entry.getKey(), true); - details.add(new UserVmDetailVO(vm.getId(), entry.getKey(), entry.getValue(), display)); + details.add(new VMInstanceDetailVO(vm.getId(), entry.getKey(), entry.getValue(), display)); } _detailsDao.saveDetails(details); @@ -755,7 +755,7 @@ public List, Pair>> getVmsD while (rs.next()) { vmsDetailByNames.add(new Pair, Pair>(new Pair( rs.getString("vm_instance.instance_name"), VirtualMachine.Type.valueOf(rs.getString("vm_type"))), - new Pair(rs.getLong("vm_instance.id"), rs.getString("user_vm_details.value")))); + new Pair(rs.getLong("vm_instance.id"), rs.getString("vm_instance_details.value")))); } } } catch (SQLException e) { @@ -782,7 +782,7 @@ public List> countVmsBySize(long dcId, int li result.add(new Ternary(rs.getInt(1), rs.getInt(2), rs.getInt(3))); } } catch (Exception e) { - logger.warn("Error counting Instances by size for dcId= " + dcId, e); + logger.warn("Error counting Instances by size for Data Center ID = " + dcId, e); } return result; } @@ -821,6 +821,7 @@ public UserVmVO persist(UserVmVO entity) { reservationDao.setResourceId(Resource.ResourceType.user_vm, userVM.getId()); reservationDao.setResourceId(Resource.ResourceType.cpu, userVM.getId()); reservationDao.setResourceId(Resource.ResourceType.memory, userVM.getId()); + reservationDao.setResourceId(Resource.ResourceType.gpu, userVM.getId()); return userVM; }); } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java index 56e16ddd871c..1a5b8cedd9ea 100755 --- a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDao.java @@ -21,6 +21,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import com.cloud.hypervisor.Hypervisor; import com.cloud.utils.Pair; @@ -118,7 +119,7 @@ public interface VMInstanceDao extends GenericDao, StateDao< List listVmsMigratingFromHost(Long hostId); - List listByZoneWithBackups(Long zoneId, Long backupOfferingId); + List listByZoneAndBackupOffering(Long zoneId, Long backupOfferingId); public Long countActiveByHostId(long hostId); @@ -190,4 +191,12 @@ List searchRemovedByRemoveDate(final Date startDate, final Date en int getVmCountByOfferingId(Long serviceOfferingId); int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List domainIds); + + List listByIdsIncludingRemoved(List ids); + + List listDeleteProtectedVmsByAccountId(long accountId); + + List listDeleteProtectedVmsByDomainIds(Set domainIds); + + List listIdsByHostIdForVolumeStats(long hostIds); } 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 518bc3cf497c..ae1e838649ba 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 @@ -25,11 +25,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import javax.annotation.PostConstruct; import javax.inject.Inject; +import org.apache.cloudstack.api.ApiConstants; import org.apache.commons.collections.CollectionUtils; import org.springframework.stereotype.Component; @@ -106,6 +108,8 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem protected SearchBuilder IdsPowerStateSelectSearch; GenericSearchBuilder CountByOfferingId; GenericSearchBuilder CountUserVmNotInDomain; + SearchBuilder DeleteProtectedVmSearchByAccount; + SearchBuilder DeleteProtectedVmSearchByDomainIds; @Inject ResourceTagDao tagsDao; @@ -133,12 +137,22 @@ public class VMInstanceDaoImpl extends GenericDaoBase implem private static final String ORDER_HOSTS_NUMBER_OF_VMS_FOR_ACCOUNT_PART2 = " GROUP BY host.id ORDER BY 2 ASC "; - private static final String COUNT_VMS_BASED_ON_VGPU_TYPES1 = + private static final String COUNT_VMS_BASED_ON_VGPU_TYPES1_LEGACY = "SELECT pci, type, SUM(vmcount) FROM (SELECT MAX(IF(offering.name = 'pciDevice',value,'')) AS pci, MAX(IF(offering.name = 'vgpuType', value,'')) " + "AS type, COUNT(DISTINCT vm.id) AS vmcount FROM service_offering_details offering INNER JOIN vm_instance vm ON offering.service_offering_id = vm.service_offering_id " + "INNER JOIN `cloud`.`host` ON vm.host_id = host.id WHERE vm.state = 'Running' AND host.data_center_id = ? "; + private static final String COUNT_VMS_BASED_ON_VGPU_TYPES2_LEGACY = + "GROUP BY vm.service_offering_id) results GROUP BY pci, type"; + + private static final String COUNT_VMS_BASED_ON_VGPU_TYPES1 = + "SELECT CONCAT(gpu_card.vendor_name, ' ', gpu_card.device_name), vgpu_profile.name, COUNT(gpu_device.vm_id) " + + "FROM `cloud`.`gpu_device` " + + "INNER JOIN `cloud`.`host` ON gpu_device.host_id = host.id " + + "INNER JOIN `cloud`.`gpu_card` ON gpu_device.card_id = gpu_card.id " + + "INNER JOIN `cloud`.`vgpu_profile` ON vgpu_profile.id = gpu_device.vgpu_profile_id " + + "WHERE vm_id IS NOT NULL AND host.data_center_id = ? "; private static final String COUNT_VMS_BASED_ON_VGPU_TYPES2 = - "GROUP BY offering.service_offering_id) results GROUP BY pci, type"; + "GROUP BY gpu_card.name, vgpu_profile.name"; private static final String UPDATE_SYSTEM_VM_TEMPLATE_ID_FOR_HYPERVISOR = "UPDATE `cloud`.`vm_instance` SET vm_template_id = ? WHERE type <> 'User' AND hypervisor_type = ? AND removed is NULL"; @@ -344,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); @@ -358,6 +373,19 @@ protected void init() { CountUserVmNotInDomain.and("domainIdsNotIn", CountUserVmNotInDomain.entity().getDomainId(), Op.NIN); CountUserVmNotInDomain.done(); + DeleteProtectedVmSearchByAccount = createSearchBuilder(); + DeleteProtectedVmSearchByAccount.selectFields(DeleteProtectedVmSearchByAccount.entity().getUuid()); + DeleteProtectedVmSearchByAccount.and(ApiConstants.ACCOUNT_ID, DeleteProtectedVmSearchByAccount.entity().getAccountId(), Op.EQ); + DeleteProtectedVmSearchByAccount.and(ApiConstants.DELETE_PROTECTION, DeleteProtectedVmSearchByAccount.entity().isDeleteProtection(), Op.EQ); + DeleteProtectedVmSearchByAccount.and(ApiConstants.REMOVED, DeleteProtectedVmSearchByAccount.entity().getRemoved(), Op.NULL); + DeleteProtectedVmSearchByAccount.done(); + + DeleteProtectedVmSearchByDomainIds = createSearchBuilder(); + DeleteProtectedVmSearchByDomainIds.selectFields(DeleteProtectedVmSearchByDomainIds.entity().getUuid()); + DeleteProtectedVmSearchByDomainIds.and(ApiConstants.DOMAIN_IDS, DeleteProtectedVmSearchByDomainIds.entity().getDomainId(), Op.IN); + DeleteProtectedVmSearchByDomainIds.and(ApiConstants.DELETE_PROTECTION, DeleteProtectedVmSearchByDomainIds.entity().isDeleteProtection(), Op.EQ); + DeleteProtectedVmSearchByDomainIds.and(ApiConstants.REMOVED, DeleteProtectedVmSearchByDomainIds.entity().getRemoved(), Op.NULL); + DeleteProtectedVmSearchByDomainIds.done(); } @Override @@ -665,7 +693,7 @@ public List listVmsMigratingFromHost(Long hostId) { } @Override - public List listByZoneWithBackups(Long zoneId, Long backupOfferingId) { + public List listByZoneAndBackupOffering(Long zoneId, Long backupOfferingId) { SearchCriteria sc = BackupSearch.create(); sc.setParameters("zone_id", zoneId); if (backupOfferingId != null) { @@ -808,40 +836,55 @@ public List listHostIdsByVmCount(long dcId, Long podId, Long clusterId, lo @Override public HashMap countVgpuVMs(Long dcId, Long podId, Long clusterId) { + StringBuilder finalQueryLegacy = new StringBuilder(); StringBuilder finalQuery = new StringBuilder(); TransactionLegacy txn = TransactionLegacy.currentTxn(); + PreparedStatement pstmtLegacy = null; PreparedStatement pstmt = null; List resourceIdList = new ArrayList(); HashMap result = new HashMap(); resourceIdList.add(dcId); + finalQueryLegacy.append(COUNT_VMS_BASED_ON_VGPU_TYPES1_LEGACY); finalQuery.append(COUNT_VMS_BASED_ON_VGPU_TYPES1); if (podId != null) { + finalQueryLegacy.append("AND host.pod_id = ? "); finalQuery.append("AND host.pod_id = ? "); resourceIdList.add(podId); } if (clusterId != null) { + finalQueryLegacy.append("AND host.cluster_id = ? "); finalQuery.append("AND host.cluster_id = ? "); resourceIdList.add(clusterId); } + finalQueryLegacy.append(COUNT_VMS_BASED_ON_VGPU_TYPES2_LEGACY); finalQuery.append(COUNT_VMS_BASED_ON_VGPU_TYPES2); try { + pstmtLegacy = txn.prepareAutoCloseStatement(finalQueryLegacy.toString()); + for (int i = 0; i < resourceIdList.size(); i++) { + pstmtLegacy.setLong(1 + i, resourceIdList.get(i)); + } + ResultSet rs = pstmtLegacy.executeQuery(); + while (rs.next()) { + result.put(rs.getString(1).concat(rs.getString(2)), rs.getLong(3)); + } + pstmt = txn.prepareAutoCloseStatement(finalQuery.toString()); for (int i = 0; i < resourceIdList.size(); i++) { pstmt.setLong(1 + i, resourceIdList.get(i)); } - ResultSet rs = pstmt.executeQuery(); + rs = pstmt.executeQuery(); while (rs.next()) { result.put(rs.getString(1).concat(rs.getString(2)), rs.getLong(3)); } return result; } catch (SQLException e) { - throw new CloudRuntimeException("DB Exception on: " + finalQuery, e); + throw new CloudRuntimeException("DB Exception on: " + finalQueryLegacy, e); } catch (Throwable e) { - throw new CloudRuntimeException("Caught: " + finalQuery, e); + throw new CloudRuntimeException("Caught: " + finalQueryLegacy, e); } } @@ -878,7 +921,7 @@ public Long countByZoneAndStateAndHostTag(long dcId, State state, String hostTag return rs.getLong(1); } } catch (Exception e) { - logger.warn(String.format("Error counting Instances by host tag for dcId= %s, hostTag= %s", dcId, hostTag), e); + logger.warn("Error counting Instances by host tag for dcId = {}, hostTag = {}", dcId, hostTag, e); } return 0L; } @@ -1063,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; @@ -1261,4 +1308,48 @@ public int getVmCountByOfferingNotInDomain(Long serviceOfferingId, List do List count = customSearch(sc, null); return count.get(0); } + + @Override + public List listByIdsIncludingRemoved(List ids) { + SearchBuilder idsSearch = createSearchBuilder(); + idsSearch.and("ids", idsSearch.entity().getId(), SearchCriteria.Op.IN); + idsSearch.done(); + SearchCriteria sc = idsSearch.create(); + sc.setParameters("ids", ids.toArray()); + return listIncludingRemovedBy(sc); + } + + @Override + public List listDeleteProtectedVmsByAccountId(long accountId) { + SearchCriteria sc = DeleteProtectedVmSearchByAccount.create(); + sc.setParameters(ApiConstants.ACCOUNT_ID, accountId); + sc.setParameters(ApiConstants.DELETE_PROTECTION, true); + Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L); + return listBy(sc, filter); + } + + @Override + public List listDeleteProtectedVmsByDomainIds(Set domainIds) { + SearchCriteria sc = DeleteProtectedVmSearchByDomainIds.create(); + sc.setParameters(ApiConstants.DOMAIN_IDS, domainIds.toArray()); + sc.setParameters(ApiConstants.DELETE_PROTECTION, true); + Filter filter = new Filter(VMInstanceVO.class, null, false, 0L, 10L); + return listBy(sc, filter); + } + + @Override + public List listIdsByHostIdForVolumeStats(long hostId) { + GenericSearchBuilder sb = createSearchBuilder(Long.class); + sb.selectFields(sb.entity().getId()); + sb.and().op("host", sb.entity().getHostId(), SearchCriteria.Op.EQ); + sb.or().op("hostNull", sb.entity().getHostId(), Op.NULL); + sb.and("lastHost", sb.entity().getLastHostId(), SearchCriteria.Op.EQ); + sb.cp(); + sb.cp(); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("host", hostId); + sc.setParameters("lastHost", hostId); + return customSearch(sc, null); + } } diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDetailsDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDetailsDao.java new file mode 100644 index 000000000000..4cbdc516ba0b --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDetailsDao.java @@ -0,0 +1,26 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.VMInstanceDetailVO; + +public interface VMInstanceDetailsDao extends GenericDao, ResourceDetailsDao { + int removeDetailsWithPrefix(long vmId, String prefix); +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDetailsDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDetailsDaoImpl.java new file mode 100644 index 000000000000..4c2fdd6f8d45 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VMInstanceDetailsDaoImpl.java @@ -0,0 +1,51 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + + +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.VMInstanceDetailVO; + +@Component +public class VMInstanceDetailsDaoImpl extends ResourceDetailsDaoBase implements VMInstanceDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new VMInstanceDetailVO(resourceId, key, value, display)); + } + + @Override + public int removeDetailsWithPrefix(long vmId, String prefix) { + if (StringUtils.isBlank(prefix)) { + return 0; + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmId", sb.entity().getResourceId(), SearchCriteria.Op.EQ); + sb.and("prefix", sb.entity().getName(), SearchCriteria.Op.LIKE); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("vmId", vmId); + sc.setParameters("prefix", prefix + "%"); + return super.remove(sc); + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java new file mode 100644 index 000000000000..a472a3b4dece --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDao.java @@ -0,0 +1,34 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import java.util.List; + +import com.cloud.utils.db.GenericDao; +import com.cloud.vm.VmIsoMapVO; + +public interface VmIsoMapDao extends GenericDao { + List listByVmId(long vmId); + + List listByIsoId(long isoId); + + VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq); + + VmIsoMapVO findByVmIdIsoId(long vmId, long isoId); + + int removeByVmId(long vmId); +} diff --git a/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java new file mode 100644 index 000000000000..44749eea75f1 --- /dev/null +++ b/engine/schema/src/main/java/com/cloud/vm/dao/VmIsoMapDaoImpl.java @@ -0,0 +1,92 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.vm.dao; + +import java.util.List; + +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.vm.VmIsoMapVO; + +@Component +public class VmIsoMapDaoImpl extends GenericDaoBase implements VmIsoMapDao { + + private SearchBuilder ListByVmId; + private SearchBuilder ListByIsoId; + private SearchBuilder ByVmIdDeviceSeq; + private SearchBuilder ByVmIdIsoId; + + protected VmIsoMapDaoImpl() { + ListByVmId = createSearchBuilder(); + ListByVmId.and("vmId", ListByVmId.entity().getVmId(), SearchCriteria.Op.EQ); + ListByVmId.done(); + + ListByIsoId = createSearchBuilder(); + ListByIsoId.and("isoId", ListByIsoId.entity().getIsoId(), SearchCriteria.Op.EQ); + ListByIsoId.done(); + + ByVmIdDeviceSeq = createSearchBuilder(); + ByVmIdDeviceSeq.and("vmId", ByVmIdDeviceSeq.entity().getVmId(), SearchCriteria.Op.EQ); + ByVmIdDeviceSeq.and("deviceSeq", ByVmIdDeviceSeq.entity().getDeviceSeq(), SearchCriteria.Op.EQ); + ByVmIdDeviceSeq.done(); + + ByVmIdIsoId = createSearchBuilder(); + ByVmIdIsoId.and("vmId", ByVmIdIsoId.entity().getVmId(), SearchCriteria.Op.EQ); + ByVmIdIsoId.and("isoId", ByVmIdIsoId.entity().getIsoId(), SearchCriteria.Op.EQ); + ByVmIdIsoId.done(); + } + + @Override + public List listByVmId(long vmId) { + SearchCriteria sc = ListByVmId.create(); + sc.setParameters("vmId", vmId); + return listBy(sc); + } + + @Override + public List listByIsoId(long isoId) { + SearchCriteria sc = ListByIsoId.create(); + sc.setParameters("isoId", isoId); + return listBy(sc); + } + + @Override + public VmIsoMapVO findByVmIdDeviceSeq(long vmId, int deviceSeq) { + SearchCriteria sc = ByVmIdDeviceSeq.create(); + sc.setParameters("vmId", vmId); + sc.setParameters("deviceSeq", deviceSeq); + return findOneBy(sc); + } + + @Override + public VmIsoMapVO findByVmIdIsoId(long vmId, long isoId) { + SearchCriteria sc = ByVmIdIsoId.create(); + sc.setParameters("vmId", vmId); + sc.setParameters("isoId", isoId); + return findOneBy(sc); + } + + @Override + public int removeByVmId(long vmId) { + SearchCriteria sc = ListByVmId.create(); + sc.setParameters("vmId", vmId); + return remove(sc); + } +} diff --git a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java index bbdbfb5b5203..39ae3d4f2fdd 100644 --- a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java +++ b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDao.java @@ -27,6 +27,8 @@ public interface VMSnapshotDao extends GenericDao, StateDao< List findByVm(Long vmId); + List findByVmAndByType(Long vmId, VMSnapshot.Type type); + List listExpungingSnapshot(); List listByInstanceId(Long vmId, VMSnapshot.State... status); @@ -35,6 +37,8 @@ public interface VMSnapshotDao extends GenericDao, StateDao< List listByParent(Long vmSnapshotId); + List listByParentAndStateIn(Long vmSnapshotId, VMSnapshot.State... states); + VMSnapshotVO findByName(Long vmId, String name); List listByAccountId(Long accountId); diff --git a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java index 48c33e089337..7f74d2338e43 100644 --- a/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java +++ b/engine/schema/src/main/java/com/cloud/vm/snapshot/dao/VMSnapshotDaoImpl.java @@ -42,6 +42,12 @@ public class VMSnapshotDaoImpl extends GenericDaoBase implem private final SearchBuilder SnapshotStatusSearch; private final SearchBuilder AllFieldsSearch; + private SearchBuilder parentIdEqAndStateIn; + + private static final String PARENT = "parent"; + + private static final String STATE = "state"; + protected VMSnapshotDaoImpl() { AllFieldsSearch = createSearchBuilder(); AllFieldsSearch.and("state", AllFieldsSearch.entity().getState(), Op.EQ); @@ -71,6 +77,11 @@ protected VMSnapshotDaoImpl() { SnapshotStatusSearch.and("vm_id", SnapshotStatusSearch.entity().getVmId(), SearchCriteria.Op.EQ); SnapshotStatusSearch.and("state", SnapshotStatusSearch.entity().getState(), SearchCriteria.Op.IN); SnapshotStatusSearch.done(); + + parentIdEqAndStateIn = createSearchBuilder(); + parentIdEqAndStateIn.and(PARENT, parentIdEqAndStateIn.entity().getParent(), Op.EQ); + parentIdEqAndStateIn.and(STATE, parentIdEqAndStateIn.entity().getState(), Op.IN); + parentIdEqAndStateIn.done(); } @Override @@ -80,6 +91,14 @@ public List findByVm(Long vmId) { return listBy(sc, null); } + @Override + public List findByVmAndByType(Long vmId, VMSnapshot.Type type) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("vm_snapshot_type", type); + return listBy(sc, null); + } + @Override public List listExpungingSnapshot() { SearchCriteria sc = ExpungingSnapshotSearch.create(); @@ -111,6 +130,14 @@ public List listByParent(Long vmSnapshotId) { return listBy(sc, null); } + @Override + public List listByParentAndStateIn(Long vmSnapshotId, State... states) { + SearchCriteria sc = parentIdEqAndStateIn.create(); + sc.setParameters(PARENT, vmSnapshotId); + sc.setParameters(STATE, (Object[])states); + return listBy(sc, null); + } + @Override public VMSnapshotVO findByName(Long vmId, String name) { SearchCriteria sc = AllFieldsSearch.create(); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java new file mode 100644 index 000000000000..7972fe6bc624 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairPermissionVO.java @@ -0,0 +1,61 @@ +// 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.acl; + +import org.apache.cloudstack.acl.apikeypair.ApiKeyPairPermission; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name = "api_keypair_permissions") +public class ApiKeyPairPermissionVO extends RolePermissionBaseVO implements ApiKeyPairPermission { + @Column(name = "api_keypair_id") + private long apiKeyPairId; + + @Column(name = "sort_order") + private long sortOrder = 0; + + public ApiKeyPairPermissionVO(long apiKeyPairId, String rule, Permission permission, String description) { + super(rule, permission, description); + this.apiKeyPairId = apiKeyPairId; + } + + public ApiKeyPairPermissionVO(String rule, Permission permission, String description) { + super(rule, permission, description); + } + + public ApiKeyPairPermissionVO() { + } + + public long getApiKeyPairId() { + return this.apiKeyPairId; + } + + public void setApiKeyPairId(long keyPairId) { + this.apiKeyPairId = keyPairId; + } + + public void setSortOrder(long sortOrder) { + this.sortOrder = sortOrder; + } + + public long getSortOrder() { + return sortOrder; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java new file mode 100644 index 000000000000..7a6bd7a3b140 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/ApiKeyPairVO.java @@ -0,0 +1,244 @@ +// 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.acl; + +import com.cloud.exception.InvalidParameterValueException; +import com.cloud.user.Account; +import com.cloud.utils.db.Encrypt; +import java.time.Instant; +import org.apache.cloudstack.acl.apikeypair.ApiKeyPair; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; +import java.util.Objects; +import java.util.UUID; +import org.joda.time.DateTime; + +@Entity +@Table(name = "api_keypair") +public class ApiKeyPairVO implements ApiKeyPair { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid = UUID.randomUUID().toString(); + + @Column(name = "user_id", nullable = false) + private Long userId; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "domain_id", nullable = false) + private Long domainId; + + @Column(name = "account_id", nullable = false) + private Long accountId; + + @Column(name = "start_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date startDate; + + @Column(name = "end_date") + @Temporal(value = TemporalType.TIMESTAMP) + private Date endDate; + + @Column(name = "created", nullable = false) + @Temporal(value = TemporalType.TIMESTAMP) + private Date created = Date.from(Instant.now()); + + @Column(name = "description", length = 1024) + private String description = ""; + + @Column(name = "api_key", nullable = false) + private String apiKey; + + @Encrypt + @Column(name = "secret_key", nullable = false) + private String secretKey; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public ApiKeyPairVO() { + } + + public ApiKeyPairVO(Long id) { + this.id = id; + } + + public ApiKeyPairVO(Long userId, String description, Date startDate, Date endDate, + String apiKey, String secretKey) { + this.userId = userId; + this.description = description; + this.startDate = startDate; + this.endDate = endDate; + this.apiKey = apiKey; + this.secretKey = secretKey; + } + + public ApiKeyPairVO(String name, Long userId, String description, Date startDate, Date endDate, Account account) { + this.name = Objects.requireNonNullElseGet(name, () -> userId + " - API key pair"); + this.userId = userId; + this.description = description; + this.startDate = startDate; + this.endDate = endDate; + this.domainId = account.getDomainId(); + this.accountId = account.getAccountId(); + } + + public ApiKeyPairVO(Long id, Long userId) { + this.id = id; + this.userId = userId; + } + + public void validateDate() { + Date now = DateTime.now().toDate(); + Date keypairStart = this.getStartDate(); + Date keypairExpiration = this.getEndDate(); + if (keypairStart != null && now.compareTo(keypairStart) <= 0) { + throw new InvalidParameterValueException(String.format("API key pair is not valid yet, start date: %s", keypairStart)); + } + if (keypairExpiration != null && now.compareTo(keypairExpiration) >= 0) { + throw new InvalidParameterValueException(String.format("API key pair is expired, expiration date: %s", keypairExpiration)); + } + } + + public boolean hasEndDatePassed() { + Date now = DateTime.now().toDate(); + Date keypairExpiration = this.getEndDate(); + return keypairExpiration != null && now.compareTo(keypairExpiration) >= 0; + } + + public long getId() { + return id; + } + + public String getUuid() { + return uuid; + } + + public Long getUserId() { + return userId; + } + + public Date getStartDate() { + return startDate; + } + + public Date getEndDate() { + return endDate; + } + + public Date getCreated() { + return created; + } + + public String getDescription() { + return description; + } + + public String getApiKey() { + return apiKey; + } + + public String getSecretKey() { + return secretKey; + } + + public Class getEntityType() { + return ApiKeyPair.class; + } + + public String getName() { + return name; + } + + public Date getRemoved() { + return removed; + } + + @Override + public long getDomainId() { + return this.domainId; + } + + @Override + public long getAccountId() { + return this.accountId; + } + + public void setId(Long id) { this.id = id; } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public void setUserId(Long userId) { + this.userId = userId; + } + + public void setStartDate(Date startDate) { + this.startDate = startDate; + } + + public void setEndDate(Date endDate) { + this.endDate = endDate; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setApiKey(String apiKey) { + this.apiKey = apiKey; + } + + public void setSecretKey(String secretKey) { + this.secretKey = secretKey; + } + + public void setName(String name) { + this.name = name; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java index f3347ab66305..588fa0299649 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/RolePermissionBaseVO.java @@ -50,6 +50,12 @@ public class RolePermissionBaseVO implements RolePermissionEntity { public RolePermissionBaseVO() { this.uuid = UUID.randomUUID().toString(); } + public RolePermissionBaseVO(final String rule, final Permission permission) { + this(); + this.rule = rule; + this.permission = permission; + } + public RolePermissionBaseVO(final String rule, final Permission permission, final String description) { this(); this.rule = rule; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairDao.java new file mode 100644 index 000000000000..006c2afbc96e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairDao.java @@ -0,0 +1,38 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.acl.dao; + +import com.cloud.utils.Pair; +import java.util.List; +import org.apache.cloudstack.acl.ApiKeyPairVO; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd; + +public interface ApiKeyPairDao extends GenericDao { + ApiKeyPairVO findBySecretKey(String secretKey); + + ApiKeyPairVO findByApiKey(String apiKey); + + ApiKeyPairVO findByUuid(String uuid); + + Pair, Integer> listApiKeysByUserOrApiKeyId(Long userId, Long apiKeyId); + + ApiKeyPairVO getLastApiKeyCreatedByUser(Long userId); + + Pair, Integer> listByUserIdsPaginated(List userIds, ListUserKeysCmd cmd); + +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairDaoImpl.java new file mode 100644 index 000000000000..a4895efed9e0 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairDaoImpl.java @@ -0,0 +1,92 @@ +// 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.acl.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import java.util.List; +import org.apache.cloudstack.acl.ApiKeyPairVO; +import org.apache.cloudstack.api.command.admin.user.ListUserKeysCmd; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +@Component +public class ApiKeyPairDaoImpl extends GenericDaoBase implements ApiKeyPairDao { + private static final String ID = "id"; + private static final String USER_ID = "userId"; + private static final String API_KEY = "apiKey"; + private static final String SECRET_KEY = "secretKey"; + + private final SearchBuilder keyPairSearch; + + ApiKeyPairDaoImpl() { + super(); + + keyPairSearch = createSearchBuilder(); + keyPairSearch.and(API_KEY, keyPairSearch.entity().getApiKey(), SearchCriteria.Op.EQ); + keyPairSearch.and(SECRET_KEY, keyPairSearch.entity().getSecretKey(), SearchCriteria.Op.EQ); + keyPairSearch.and(ID, keyPairSearch.entity().getId(), SearchCriteria.Op.EQ); + keyPairSearch.and(USER_ID, keyPairSearch.entity().getUserId(), SearchCriteria.Op.IN); + keyPairSearch.done(); + } + + @Override + public ApiKeyPairVO findByApiKey(String apiKey) { + SearchCriteria sc = keyPairSearch.create(); + sc.setParameters(API_KEY, apiKey); + return findOneBy(sc); + } + + public ApiKeyPairVO findBySecretKey(String secretKey) { + SearchCriteria sc = keyPairSearch.create(); + sc.setParameters(SECRET_KEY, secretKey); + return findOneBy(sc); + } + + public Pair, Integer> listApiKeysByUserOrApiKeyId(Long userId, Long apiKeyId) { + SearchCriteria sc = keyPairSearch.create(); + sc.setParametersIfNotNull(USER_ID, userId); + sc.setParametersIfNotNull(ID, apiKeyId); + final Filter searchFilter = new Filter(100); + return searchAndCount(sc, searchFilter); + } + + public ApiKeyPairVO getLastApiKeyCreatedByUser(Long userId) { + final SearchCriteria sc = keyPairSearch.create(); + sc.setParametersIfNotNull(USER_ID, userId); + final Filter searchBySorted = new Filter(ApiKeyPairVO.class, ID, false, null, null); + return findOneBy(sc, searchBySorted); + } + + public Pair, Integer> listByUserIdsPaginated(List userIds, ListUserKeysCmd cmd) { + Long pageSizeVal = cmd.getPageSizeVal(); + Long startIndex = cmd.getStartIndex(); + Filter searchFilter = new Filter(ApiKeyPairVO.class, ID, true, startIndex, pageSizeVal); + + final SearchCriteria sc = keyPairSearch.create(); + sc.setParameters(USER_ID, (Object[]) userIds.toArray(new Long[0])); + + Pair, Integer> apiKeyPairVOList = searchAndCount(sc, searchFilter); + if (CollectionUtils.isEmpty(apiKeyPairVOList.first())) { + return new Pair<>(List.of(), 0); + } + return apiKeyPairVOList; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairPermissionsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairPermissionsDao.java new file mode 100644 index 000000000000..cbca2fd72747 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairPermissionsDao.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.acl.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.acl.ApiKeyPairPermissionVO; + +import java.util.List; + +public interface ApiKeyPairPermissionsDao extends GenericDao { + List findAllByApiKeyPairId(Long apiKeyPairId); + + List findAllByKeyPairIdSorted(Long apiKeyPairId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairPermissionsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairPermissionsDaoImpl.java new file mode 100644 index 000000000000..4510e0ae2896 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/acl/dao/ApiKeyPairPermissionsDaoImpl.java @@ -0,0 +1,71 @@ +// 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.acl.dao; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import java.util.Collections; +import java.util.Objects; +import org.apache.commons.collections.CollectionUtils; +import org.apache.cloudstack.acl.ApiKeyPairPermissionVO; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class ApiKeyPairPermissionsDaoImpl extends GenericDaoBase implements ApiKeyPairPermissionsDao { + private static final String API_KEY_PAIR_ID = "apiKeyPairId"; + private static final String SORT_ORDER = "sortOrder"; + + private final SearchBuilder permissionByApiKeyPairIdSearch; + + public ApiKeyPairPermissionsDaoImpl() { + super(); + + permissionByApiKeyPairIdSearch = createSearchBuilder(); + permissionByApiKeyPairIdSearch.and(API_KEY_PAIR_ID, permissionByApiKeyPairIdSearch.entity().getApiKeyPairId(), SearchCriteria.Op.EQ); + permissionByApiKeyPairIdSearch.done(); + } + + public List findAllByApiKeyPairId(Long apiKeyPairId) { + SearchCriteria sc = permissionByApiKeyPairIdSearch.create(); + sc.setParameters(API_KEY_PAIR_ID, String.valueOf(apiKeyPairId)); + return listBy(sc); + } + + @Override + public ApiKeyPairPermissionVO persist(final ApiKeyPairPermissionVO item) { + item.setSortOrder(0); + final List permissionsList = findAllByKeyPairIdSorted(item.getApiKeyPairId()); + if (!CollectionUtils.isEmpty(permissionsList)) { + ApiKeyPairPermissionVO lastPermission = permissionsList.get(permissionsList.size() - 1); + item.setSortOrder(lastPermission.getSortOrder() + 1); + } + return super.persist(item); + } + + @Override + public List findAllByKeyPairIdSorted(Long apiKeyPairId) { + final SearchCriteria sc = permissionByApiKeyPairIdSearch.create(); + sc.setParameters(API_KEY_PAIR_ID, apiKeyPairId); + final Filter searchBySorted = new Filter(ApiKeyPairPermissionVO.class, SORT_ORDER, true, null, null); + final List apiKeyPairPermissionList = listBy(sc, searchBySorted); + return Objects.requireNonNullElse(apiKeyPairPermissionList, Collections.emptyList()); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java new file mode 100644 index 000000000000..aaf63518708c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupDetailVO.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.ResourceDetail; + +@Entity +@Table(name = "backup_details") +public class BackupDetailVO implements ResourceDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value", length = 65536) + private String value; + + @Column(name = "display") + private boolean display = true; + + public BackupDetailVO() { + } + + public BackupDetailVO(long backupId, String name, String value, boolean display) { + this.resourceId = backupId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getId() { + return id; + } + + @Override + public long getResourceId() { + return resourceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public boolean isDisplay() { + return display; + } + + public void setId(long id) { + this.id = id; + } + + public void setResourceId(long resourceId) { + this.resourceId = resourceId; + } + + public void setName(String name) { + this.name = name; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java new file mode 100644 index 000000000000..6bdf7602a9d4 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingDetailsVO.java @@ -0,0 +1,86 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +import org.apache.cloudstack.api.ResourceDetail; + +@Entity +@Table(name = "backup_offering_details") +public class BackupOfferingDetailsVO implements ResourceDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_offering_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value") + private String value; + + @Column(name = "display") + private boolean display = true; + + protected BackupOfferingDetailsVO() { + } + + public BackupOfferingDetailsVO(long backupOfferingId, String name, String value, boolean display) { + this.resourceId = backupOfferingId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getResourceId() { + return resourceId; + } + + public void setResourceId(long backupOfferingId) { + this.resourceId = backupOfferingId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public long getId() { + return id; + } + + @Override + public boolean isDisplay() { + return display; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java index d30385af575d..9156f8f5ff84 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupOfferingVO.java @@ -17,6 +17,8 @@ package org.apache.cloudstack.backup; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + import java.util.Date; import java.util.UUID; @@ -81,6 +83,11 @@ public BackupOfferingVO(final long zoneId, final String externalId, final String this.created = new Date(); } + public BackupOfferingVO(final long zoneId, final String provider, final String name, final String description, final boolean userDrivenBackupAllowed) { + this(zoneId, null, provider, name, description, userDrivenBackupAllowed); + this.externalId = this.uuid; + } + public String getUuid() { return uuid; } @@ -131,4 +138,9 @@ public void setDescription(String description) { public Date getCreated() { return created; } + + @Override + public String toString() { + return String.format("Backup offering %s.", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "name", "uuid")); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java index e8364520ed05..1764496a6c0b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupRepositoryVO.java @@ -67,6 +67,9 @@ public class BackupRepositoryVO implements BackupRepository { @Column(name = "capacity_bytes", nullable = true) private Long capacityBytes; + @Column(name = "cross_zone_instance_creation") + private Boolean crossZoneInstanceCreation; + @Column(name = "created") @Temporal(value = TemporalType.TIMESTAMP) private Date created; @@ -79,7 +82,7 @@ public BackupRepositoryVO() { this.uuid = UUID.randomUUID().toString(); } - public BackupRepositoryVO(final long zoneId, final String provider, final String name, final String type, final String address, final String mountOptions, final Long capacityBytes) { + public BackupRepositoryVO(final long zoneId, final String provider, final String name, final String type, final String address, final String mountOptions, final Long capacityBytes, final Boolean crossZoneInstanceCreation) { this(); this.zoneId = zoneId; this.provider = provider; @@ -88,6 +91,7 @@ public BackupRepositoryVO(final long zoneId, final String provider, final String this.address = address; this.mountOptions = mountOptions; this.capacityBytes = capacityBytes; + this.crossZoneInstanceCreation = crossZoneInstanceCreation; this.created = new Date(); } @@ -139,16 +143,41 @@ public String getMountOptions() { return mountOptions; } + @Override + public void setMountOptions(String mountOptions) { + this.mountOptions = mountOptions; + } + @Override public Long getUsedBytes() { return usedBytes; } + @Override + public void setUsedBytes(Long usedBytes) { + this.usedBytes = usedBytes; + } + @Override public Long getCapacityBytes() { return capacityBytes; } + @Override + public Boolean crossZoneInstanceCreationEnabled() { + return crossZoneInstanceCreation; + } + + @Override + public void setCrossZoneInstanceCreation(Boolean crossZoneInstanceCreation) { + this.crossZoneInstanceCreation = crossZoneInstanceCreation; + } + + @Override + public void setCapacityBytes(Long capacityBytes) { + this.capacityBytes = capacityBytes; + } + public Date getCreated() { return created; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java index fd3c0be18d22..aa02bb077163 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupScheduleVO.java @@ -18,6 +18,7 @@ package org.apache.cloudstack.backup; import java.util.Date; +import java.util.UUID; import javax.persistence.Column; import javax.persistence.Entity; @@ -39,6 +40,9 @@ public class BackupScheduleVO implements BackupSchedule { @Column(name = "id") private long id; + @Column(name = "uuid", nullable = false) + private String uuid = UUID.randomUUID().toString(); + @Column(name = "vm_id") private Long vmId; @@ -58,15 +62,36 @@ public class BackupScheduleVO implements BackupSchedule { @Column(name = "async_job_id") Long asyncJobId; + @Column(name = "max_backups") + private int maxBackups = 0; + + @Column(name = "quiescevm") + Boolean quiesceVM = false; + + @Column(name = "account_id") + Long accountId; + + @Column(name = "domain_id") + Long domainId; + + @Column(name = "isolated") + private boolean isolated; + public BackupScheduleVO() { } - public BackupScheduleVO(Long vmId, DateUtil.IntervalType scheduleType, String schedule, String timezone, Date scheduledTimestamp) { + public BackupScheduleVO(Long vmId, DateUtil.IntervalType scheduleType, String schedule, String timezone, Date scheduledTimestamp, int maxBackups, Boolean quiesceVM, + Long accountId, Long domainId, boolean isolated) { this.vmId = vmId; this.scheduleType = (short) scheduleType.ordinal(); this.schedule = schedule; this.timezone = timezone; this.scheduledTimestamp = scheduledTimestamp; + this.maxBackups = maxBackups; + this.quiesceVM = quiesceVM; + this.accountId = accountId; + this.domainId = domainId; + this.isolated = isolated; } @Override @@ -80,6 +105,11 @@ public long getId() { return id; } + @Override + public String getUuid() { + return uuid; + } + public Long getVmId() { return vmId; } @@ -128,4 +158,57 @@ public Long getAsyncJobId() { public void setAsyncJobId(Long asyncJobId) { this.asyncJobId = asyncJobId; } + + public int getMaxBackups() { + return maxBackups; + } + + public void setMaxBackups(int maxBackups) { + this.maxBackups = maxBackups; + } + + public void setQuiesceVM(Boolean quiesceVM) { + this.quiesceVM = quiesceVM; + } + + public Boolean getQuiesceVM() { + return quiesceVM; + } + + @Override + public Class getEntityType() { + return BackupSchedule.class; + } + + @Override + public String getName() { + return null; + } + + @Override + public long getDomainId() { + return domainId; + } + + @Override + public long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + @Override + public boolean isIsolated() { + return isolated; + } + + public void setIsolated(boolean isolated) { + this.isolated = isolated; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java index b4cd2f7badae..b4cad92d8770 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/BackupVO.java @@ -26,6 +26,7 @@ import java.util.Collections; import java.util.Date; import java.util.List; +import java.util.Map; import java.util.UUID; import javax.persistence.Column; @@ -38,6 +39,7 @@ import javax.persistence.Table; import javax.persistence.Temporal; import javax.persistence.TemporalType; +import javax.persistence.Transient; @Entity @Table(name = "backups") @@ -47,17 +49,23 @@ public class BackupVO implements Backup { @Column(name = "id") private long id; + @Column(name = "name") + private String name; + + @Column(name = "description") + private String description; + @Column(name = "uuid") private String uuid; @Column(name = "vm_id") - private long vmId; + private Long vmId; @Column(name = "external_id") private String externalId; @Column(name = "type") - private String backupType; + private String type; @Column(name = "date") @Temporal(value = TemporalType.DATE) @@ -72,6 +80,9 @@ public class BackupVO implements Backup { @Column(name = "protected_size") private Long protectedSize; + @Column(name = "uncompressed_size") + private Long uncompressedSize; + @Enumerated(value = EnumType.STRING) @Column(name = "status") private Backup.Status status; @@ -91,14 +102,55 @@ public class BackupVO implements Backup { @Column(name = "backed_volumes", length = 65535) protected String backedUpVolumes; + @Column(name = "backup_schedule_id") + private Long backupScheduleId; + + @Column(name = "compression_status") + private CompressionStatus compressionStatus; + + @Column(name = "validation_status") + private ValidationStatus validationStatus; + + @Column(name = "from_checkpoint_id") + private String fromCheckpointId; + + @Column(name = "to_checkpoint_id") + private String toCheckpointId; + + @Column(name = "checkpoint_create_time") + private Long checkpointCreateTime; + + @Column(name = "host_id") + private Long hostId; + + @Transient + Map details; + public BackupVO() { this.uuid = UUID.randomUUID().toString(); } + public BackupVO(String name, long vmId, long backupOfferingId, long accountId, long domainId, long zoneId, long virtualSize, + Status status, Long backupScheduleId, CompressionStatus compressionStatus, ValidationStatus validationStatus) { + this.name = name; + this.vmId = vmId; + this.backupOfferingId = backupOfferingId; + this.accountId = accountId; + this.domainId = domainId; + this.zoneId = zoneId; + this.protectedSize = virtualSize; + this.status = status; + this.setType("FULL"); + this.uuid = UUID.randomUUID().toString(); + this.backupScheduleId = backupScheduleId; + this.compressionStatus = compressionStatus; + this.validationStatus = validationStatus; + } + @Override public String toString() { return String.format("Backup %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( - this, "id", "uuid", "vmId", "backupType", "externalId")); + this, "id", "uuid", "vmId", "type", "externalId")); } @Override @@ -112,11 +164,11 @@ public String getUuid() { } @Override - public long getVmId() { + public Long getVmId() { return vmId; } - public void setVmId(long vmId) { + public void setVmId(Long vmId) { this.vmId = vmId; } @@ -129,12 +181,13 @@ public void setExternalId(String externalId) { this.externalId = externalId; } + @Override public String getType() { - return backupType; + return type; } public void setType(String type) { - this.backupType = type; + this.type = type; } @Override @@ -215,7 +268,22 @@ public Class getEntityType() { @Override public String getName() { - return null; + return name; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public void setDescription(String description) { + this.description = description; } public List getBackedUpVolumes() { @@ -229,11 +297,95 @@ public void setBackedUpVolumes(String backedUpVolumes) { this.backedUpVolumes = backedUpVolumes; } + @Override + public Map getDetails() { + return details; + } + + @Override + public String getDetail(String name) { + return this.details.get(name); + } + + public void setDetails(Map details) { + this.details = details; + } + public Date getRemoved() { return removed; } - public void setRemoved(Date removed) { this.removed = removed; } + + @Override + public Long getBackupScheduleId() { + return backupScheduleId; + } + + public void setBackupScheduleId(Long backupScheduleId) { + this.backupScheduleId = backupScheduleId; + } + + @Override + public CompressionStatus getCompressionStatus() { + return compressionStatus; + } + + public void setCompressionStatus(CompressionStatus compressionStatus) { + this.compressionStatus = compressionStatus; + } + + @Override + public ValidationStatus getValidationStatus() { + return validationStatus; + } + + public void setValidationStatus(ValidationStatus validationStatus) { + this.validationStatus = validationStatus; + } + + public Long getUncompressedSize() { + return uncompressedSize; + } + + public void setUncompressedSize(Long uncompressedSize) { + this.uncompressedSize = uncompressedSize; + } + + @Override + public String getFromCheckpointId() { + return fromCheckpointId; + } + + public void setFromCheckpointId(String fromCheckpointId) { + this.fromCheckpointId = fromCheckpointId; + } + + @Override + public String getToCheckpointId() { + return toCheckpointId; + } + + public void setToCheckpointId(String toCheckpointId) { + this.toCheckpointId = toCheckpointId; + } + + @Override + public Long getCheckpointCreateTime() { + return checkpointCreateTime; + } + + public void setCheckpointCreateTime(Long checkpointCreateTime) { + this.checkpointCreateTime = checkpointCreateTime; + } + + @Override + public Long getHostId() { + return hostId; + } + + public void setHostId(Long hostId) { + this.hostId = hostId; + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java new file mode 100644 index 000000000000..ec9b927b63e4 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/ImageTransferVO.java @@ -0,0 +1,242 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +@Entity +@Table(name = "image_transfer") +public class ImageTransferVO implements ImageTransfer { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "backup_id") + private Long backupId; + + @Column(name = "volume_id") + private long volumeId; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "socket") + private String socket; + + @Column(name = "file") + private String file; + + @Column(name = "transfer_url") + private String transferUrl; + + @Enumerated(value = EnumType.STRING) + @Column(name = "phase") + private Phase phase; + + @Enumerated(value = EnumType.STRING) + @Column(name = "direction") + private Direction direction; + + @Enumerated(value = EnumType.STRING) + @Column(name = "backend") + private Backend backend; + + @Column(name = "signed_ticket_id") + private String signedTicketId; + + @Column(name = "account_id") + Long accountId; + + @Column(name = "domain_id") + Long domainId; + + @Column(name = "data_center_id") + Long dataCenterId; + + @Column(name = "created") + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = "updated") + @Temporal(value = TemporalType.TIMESTAMP) + private Date updated; + + @Column(name = "removed") + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public ImageTransferVO() { + } + + private ImageTransferVO(String uuid, long volumeId, long hostId, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) { + this.uuid = uuid; + this.volumeId = volumeId; + this.hostId = hostId; + this.phase = phase; + this.direction = direction; + this.accountId = accountId; + this.domainId = domainId; + this.dataCenterId = dataCenterId; + this.created = new Date(); + } + + public ImageTransferVO(String uuid, Long backupId, long volumeId, long hostId, String socket, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) { + this(uuid, volumeId, hostId, phase, direction, accountId, domainId, dataCenterId); + this.backupId = backupId; + this.socket = socket; + this.backend = Backend.nbd; + } + + public ImageTransferVO(String uuid, long volumeId, long hostId, String file, Phase phase, Direction direction, Long accountId, Long domainId, Long dataCenterId) { + this(uuid, volumeId, hostId, phase, direction, accountId, domainId, dataCenterId); + this.file = file; + this.backend = Backend.file; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public Long getBackupId() { + return backupId; + } + + public void setBackupId(long backupId) { + this.backupId = backupId; + } + + @Override + public long getVolumeId() { + return volumeId; + } + + public void setVolumeId(long volumeId) { + this.volumeId = volumeId; + } + + @Override + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + public void setSocket(String socket) { + this.socket = socket; + } + + @Override + public String getTransferUrl() { + return transferUrl; + } + + public void setTransferUrl(String transferUrl) { + this.transferUrl = transferUrl; + } + + @Override + public Phase getPhase() { + return phase; + } + + public void setPhase(Phase phase) { + this.phase = phase; + this.updated = new Date(); + } + + @Override + public Direction getDirection() { + return direction; + } + + public void setDirection(Direction direction) { + this.direction = direction; + } + + @Override + public Backend getBackend() { + return backend; + } + + @Override + public String getSignedTicketId() { + return signedTicketId; + } + + public void setSignedTicketId(String signedTicketId) { + this.signedTicketId = signedTicketId; + } + + @Override + public Class getEntityType() { + return ImageTransfer.class; + } + + @Override + public String getName() { + return null; + } + + @Override + public long getDomainId() { + return domainId; + } + + @Override + public long getAccountId() { + return accountId; + } + + @Override + public long getDataCenterId() { + return dataCenterId; + } + + public Date getCreated() { + return created; + } + + public Date getUpdated() { + return updated; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupDataStoreVO.java new file mode 100644 index 000000000000..5266a4129905 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupDataStoreVO.java @@ -0,0 +1,95 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup; + +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.persistence.Column; +import javax.persistence.Entity; + +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + + +@Entity +@Table(name = "internal_backup_store_ref") +public class InternalBackupDataStoreVO implements InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_id") + private long backupId; + + @Column(name = "volume_id") + private long volumeId; + + @Column (name = "device_id") + private long deviceId; + + @Column(name = "path") + private String backupPath; + + public InternalBackupDataStoreVO() { + } + + public InternalBackupDataStoreVO(long backupId, long volumeId, long deviceId, String backupPath) { + this.backupId = backupId; + this.volumeId = volumeId; + this.deviceId = deviceId; + this.backupPath = backupPath; + } + + @Override + public long getId() { + return id; + } + + public long getBackupId() { + return backupId; + } + + public long getVolumeId() { + return volumeId; + } + + public long getDeviceId() { + return deviceId; + } + + public String getBackupPath() { + return backupPath; + } + + public void setVolumeId(long volumeId) { + this.volumeId = volumeId; + } + + public void setBackupPath(String backupPath) { + this.backupPath = backupPath; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java new file mode 100644 index 000000000000..e9e232ef2023 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupJoinVO.java @@ -0,0 +1,211 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.backup; + +import com.google.gson.Gson; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; +import org.apache.commons.lang3.StringUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Arrays; +import java.util.Collections; +import java.util.Date; +import java.util.List; + +@Entity +@Table(name = "internal_backup_view") +public class InternalBackupJoinVO { + + @Id + @Column(name="id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "vm_id") + private long vmId; + + @Column(name = "backed_volumes", length = 65535) + private String backedUpVolumes; + + @Column(name = "backup_offering_id") + private long backupOfferingId; + + @Column(name = "image_store_id") + private long imageStoreId; + + @Column(name = "parent_id") + private long parentId; + + @Column(name = "type") + private String type; + + @Column(name = "date") + @Temporal(value = TemporalType.DATE) + private Date date; + + @Enumerated(value = EnumType.STRING) + @Column(name = "status") + private Backup.Status status; + + @Enumerated(value = EnumType.STRING) + @Column(name = "compression_status") + private Backup.CompressionStatus compressionStatus; + + @Column(name = "end_of_chain") + private Boolean endOfChain; + + @Column(name = "current") + private Boolean current; + + @Column(name = "image_store_path") + private String imageStorePath; + + @Column(name = "zone_id") + private long zoneId; + + @Column(name = "size") + private long size; + + @Column(name = "protected_size") + private long protectedSize; + + @Column(name = "volume_id") + private long volumeId; + + @Column(name = "isolated") + private Boolean isolated; + + @Column(name = "storage_pool_delta_path") + private String storagePoolDeltaPath; + + @Column(name = "storage_pool_parent_path") + private String storagePoolParentPath; + + @Column(name = "schedule_id") + private Long scheduleId; + + public InternalBackupJoinVO() { + } + + public long getId() { + return id; + } + + public String getUuid() { + return uuid; + } + + public long getVmId() { + return vmId; + } + + public List getBackedUpVolumes() { + if (StringUtils.isEmpty(this.backedUpVolumes)) { + return Collections.emptyList(); + } + return Arrays.asList(new Gson().fromJson(this.backedUpVolumes, Backup.VolumeInfo[].class)); + } + + public long getBackupOfferingId() { + return backupOfferingId; + } + + public long getImageStoreId() { + return imageStoreId; + } + + public long getParentId() { + return parentId; + } + + public String getType() { + return type; + } + + public Date getDate() { + return date; + } + + public Backup.Status getStatus() { + return status; + } + + public Boolean getEndOfChain() { + return BooleanUtils.isTrue(endOfChain); + } + + public Boolean getCurrent() { + return BooleanUtils.isTrue(current); + } + + public String getImageStorePath() { + return imageStorePath; + } + + public long getZoneId() { + return zoneId; + } + + public long getSize() { + return size; + } + + public long getProtectedSize() { + return protectedSize; + } + + public long getVolumeId() { + return volumeId; + } + + public Boolean getIsolated() { + return BooleanUtils.isTrue(isolated); + } + + public Backup.CompressionStatus getCompressionStatus() { + return compressionStatus; + } + + public String getStoragePoolDeltaPath() { + return storagePoolDeltaPath; + } + + public String getStoragePoolParentPath() { + return storagePoolParentPath; + } + + public Long getScheduleId() { + return scheduleId; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupServiceJobType.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupServiceJobType.java new file mode 100644 index 000000000000..287b64c2e6fc --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupServiceJobType.java @@ -0,0 +1,21 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +public enum InternalBackupServiceJobType { + StartCompression, FinalizeCompression, BackupValidation +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupServiceJobVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupServiceJobVO.java new file mode 100644 index 000000000000..87b4a53cb615 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupServiceJobVO.java @@ -0,0 +1,179 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "internal_backup_service_job") +public class InternalBackupServiceJobVO implements InternalIdentity, Comparable { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_id") + private long backupId; + + @Column(name = "instance_id") + private long instanceId; + + @Column(name = "account_id") + private long accountId; + + @Column(name = "host_id") + private Long hostId; + + @Column(name = "zone_id") + private long zoneId; + + @Column(name = "attempts") + private int attempts; + + @Column (name = "type") + private InternalBackupServiceJobType type; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name = "scheduled_start_time") + @Temporal(value = TemporalType.TIMESTAMP) + private Date scheduledStartTime; + + @Column(name = "start_time") + @Temporal(value = TemporalType.TIMESTAMP) + private Date startTime; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + + public InternalBackupServiceJobVO() { + } + + public InternalBackupServiceJobVO(long backupId, long zoneId, long instanceId, long accountId, InternalBackupServiceJobType type) { + this.created = new Date(); + this.backupId = backupId; + this.zoneId = zoneId; + this.instanceId = instanceId; + this.accountId = accountId; + this.type = type; + this.scheduledStartTime = this.created; + } + + public InternalBackupServiceJobVO(long backupId, long zoneId, long instanceId, long accountId, InternalBackupServiceJobType type, Date scheduledStartTime) { + this(backupId, zoneId, instanceId, accountId, type); + this.scheduledStartTime = scheduledStartTime; + } + + @Override + public long getId() { + return id; + } + + public long getBackupId() { + return backupId; + } + + public long getInstanceId() { + return instanceId; + } + + public long getAccountId() { + return accountId; + } + + public Long getHostId() { + return hostId; + } + + public void setHostId(Long hostId) { + this.hostId = hostId; + } + + public long getZoneId() { + return zoneId; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public int getAttempts() { + return attempts; + } + + public void setAttempts(int attempts) { + this.attempts = attempts; + } + + public InternalBackupServiceJobType getType() { + return type; + } + + public Date getCreated() { + return created; + } + + public Date getScheduledStartTime() { + return scheduledStartTime; + } + + public void setScheduledStartTime(Date scheduledStartTime) { + this.scheduledStartTime = scheduledStartTime; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public int compareTo(InternalBackupServiceJobVO that) { + return this.created.compareTo(that.created); + } + + @Override + public String toString() { + return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "backupId", "zoneId", "hostId", "created", "scheduledStartTime", "startTime", "attempts", + "type"); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupStoragePoolVO.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupStoragePoolVO.java new file mode 100644 index 000000000000..f6d4cd10ffb7 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/InternalBackupStoragePoolVO.java @@ -0,0 +1,101 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup; + +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.commons.lang3.builder.ReflectionToStringBuilder; +import org.apache.commons.lang3.builder.ToStringStyle; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "internal_backup_pool_ref") +public class InternalBackupStoragePoolVO implements InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "backup_id") + private long backupId; + + @Column(name = "storage_pool_id") + private long storagePoolId; + + @Column(name = "volume_id") + private long volumeId; + + @Column(name = "backup_delta_path") + private String backupDeltaPath; + + @Column(name = "backup_parent_path") + private String backupDeltaParentPath; + + public InternalBackupStoragePoolVO() { + } + + public InternalBackupStoragePoolVO(long backupId, long storagePoolId, long volumeId, String backupDeltaPath, String backupDeltaParentPath) { + this.backupId = backupId; + this.storagePoolId = storagePoolId; + this.volumeId = volumeId; + this.backupDeltaPath = backupDeltaPath; + this.backupDeltaParentPath = backupDeltaParentPath; + } + + @Override + public long getId() { + return id; + } + + public long getBackupId() { + return backupId; + } + + public long getStoragePoolId() { + return storagePoolId; + } + + public long getVolumeId() { + return volumeId; + } + + public String getBackupDeltaPath() { + return backupDeltaPath; + } + + public String getBackupDeltaParentPath() { + return backupDeltaParentPath; + } + + public void setBackupDeltaPath(String backupDeltaPath) { + this.backupDeltaPath = backupDeltaPath; + } + + public void setBackupDeltaParentPath(String backupDeltaParentPath) { + this.backupDeltaParentPath = backupDeltaParentPath; + } + + @Override + public String toString() { + return ReflectionToStringBuilder.toString(this, ToStringStyle.JSON_STYLE); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java index 89a13245b0a0..3829777d9d8f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDao.java @@ -19,7 +19,6 @@ import java.util.List; -import org.apache.cloudstack.api.response.BackupResponse; import org.apache.cloudstack.backup.Backup; import org.apache.cloudstack.backup.BackupVO; @@ -33,7 +32,15 @@ public interface BackupDao extends GenericDao { List listByVmId(Long zoneId, Long vmId); List listByAccountId(Long accountId); List syncBackups(Long zoneId, Long vmId, List externalBackups); + List listByVmIdAndOffering(Long zoneId, Long vmId, Long offeringId); + List searchByVmIds(List vmIds); BackupVO getBackupVO(Backup backup); List listByOfferingId(Long backupOfferingId); - BackupResponse newBackupResponse(Backup backup); + List listVmIdsWithBackupsInZone(Long zoneId); + public Long countBackupsForAccount(long accountId); + public Long calculateBackupStorageForAccount(long accountId); + void loadDetails(BackupVO backup); + void saveDetails(BackupVO backup); + List listBySchedule(Long backupScheduleId); + BackupVO findLatestByStatusAndVmId(Backup.Status status, long vmId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java index 5a9cd0620374..9859f29701b7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDaoImpl.java @@ -19,28 +19,31 @@ import java.util.ArrayList; import java.util.List; -import java.util.Objects; +import java.util.Map; import javax.annotation.PostConstruct; import javax.inject.Inject; -import org.apache.cloudstack.api.response.BackupResponse; +import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericSearchBuilder; + import org.apache.cloudstack.backup.Backup; -import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.BackupDetailVO; import org.apache.cloudstack.backup.BackupVO; +import org.apache.commons.collections.CollectionUtils; -import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.DataCenterDao; -import com.cloud.domain.DomainVO; import com.cloud.domain.dao.DomainDao; -import com.cloud.user.AccountVO; import com.cloud.user.dao.AccountDao; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import com.cloud.vm.VMInstanceVO; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; import com.cloud.vm.dao.VMInstanceDao; -import com.google.gson.Gson; +import com.cloud.network.dao.NetworkDao; public class BackupDaoImpl extends GenericDaoBase implements BackupDao { @@ -56,10 +59,26 @@ public class BackupDaoImpl extends GenericDaoBase implements Bac @Inject VMInstanceDao vmInstanceDao; + @Inject + private VMTemplateDao templateDao; + @Inject BackupOfferingDao backupOfferingDao; + @Inject + BackupDetailsDao backupDetailsDao; + + @Inject + ServiceOfferingDao serviceOfferingDao; + + @Inject + NetworkDao networkDao; + private SearchBuilder backupSearch; + private GenericSearchBuilder CountBackupsByAccount; + private GenericSearchBuilder CalculateBackupStorageByAccount; + private SearchBuilder listBackupsBySchedule; + private GenericSearchBuilder backupVmSearchInZone; public BackupDaoImpl() { } @@ -71,7 +90,33 @@ protected void init() { backupSearch.and("external_id", backupSearch.entity().getExternalId(), SearchCriteria.Op.EQ); backupSearch.and("backup_offering_id", backupSearch.entity().getBackupOfferingId(), SearchCriteria.Op.EQ); backupSearch.and("zone_id", backupSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + backupSearch.and("status", backupSearch.entity().getStatus(), SearchCriteria.Op.IN); backupSearch.done(); + + backupVmSearchInZone = createSearchBuilder(Long.class); + backupVmSearchInZone.select(null, SearchCriteria.Func.DISTINCT, backupVmSearchInZone.entity().getVmId()); + backupVmSearchInZone.and("zone_id", backupVmSearchInZone.entity().getZoneId(), SearchCriteria.Op.EQ); + backupVmSearchInZone.done(); + + CountBackupsByAccount = createSearchBuilder(Long.class); + CountBackupsByAccount.select(null, SearchCriteria.Func.COUNT, null); + CountBackupsByAccount.and("account", CountBackupsByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + CountBackupsByAccount.and("status", CountBackupsByAccount.entity().getStatus(), SearchCriteria.Op.NIN); + CountBackupsByAccount.and("removed", CountBackupsByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); + CountBackupsByAccount.done(); + + CalculateBackupStorageByAccount = createSearchBuilder(SumCount.class); + CalculateBackupStorageByAccount.select("sum", SearchCriteria.Func.SUM, CalculateBackupStorageByAccount.entity().getSize()); + CalculateBackupStorageByAccount.and("account", CalculateBackupStorageByAccount.entity().getAccountId(), SearchCriteria.Op.EQ); + CalculateBackupStorageByAccount.and("status", CalculateBackupStorageByAccount.entity().getStatus(), SearchCriteria.Op.NIN); + CalculateBackupStorageByAccount.and("removed", CalculateBackupStorageByAccount.entity().getRemoved(), SearchCriteria.Op.NULL); + CalculateBackupStorageByAccount.done(); + + listBackupsBySchedule = createSearchBuilder(); + listBackupsBySchedule.and("backup_schedule_id", listBackupsBySchedule.entity().getBackupScheduleId(), SearchCriteria.Op.EQ); + listBackupsBySchedule.and("status", listBackupsBySchedule.entity().getStatus(), SearchCriteria.Op.EQ); + listBackupsBySchedule.and("removed", listBackupsBySchedule.entity().getRemoved(), SearchCriteria.Op.NULL); + listBackupsBySchedule.done(); } @Override @@ -105,6 +150,17 @@ public List listByVmId(Long zoneId, Long vmId) { return new ArrayList<>(listBy(sc)); } + @Override + public List listByVmIdAndOffering(Long zoneId, Long vmId, Long offeringId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + if (zoneId != null) { + sc.setParameters("zone_id", zoneId); + } + sc.setParameters("backup_offering_id", offeringId); + return new ArrayList<>(listBy(sc)); + } + private Backup findByExternalId(Long zoneId, String externalId) { SearchCriteria sc = backupSearch.create(); sc.setParameters("external_id", externalId); @@ -112,6 +168,18 @@ private Backup findByExternalId(Long zoneId, String externalId) { return findOneBy(sc); } + @Override + public List searchByVmIds(List vmIds) { + if (CollectionUtils.isEmpty(vmIds)) { + return new ArrayList<>(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("vmIds", sb.entity().getVmId(), SearchCriteria.Op.IN); + SearchCriteria sc = sb.create(); + sc.setParameters("vmIds", vmIds.toArray()); + return search(sc, null); + } + public BackupVO getBackupVO(Backup backup) { BackupVO backupVO = new BackupVO(); backupVO.setExternalId(backup.getExternalId()); @@ -133,6 +201,27 @@ public void removeExistingBackups(Long zoneId, Long vmId) { expunge(sc); } + @Override + public BackupVO persist(BackupVO backup) { + return Transaction.execute((TransactionCallback) status -> { + BackupVO backupDb = super.persist(backup); + saveDetails(backup); + loadDetails(backupDb); + return backupDb; + }); + } + + @Override + public boolean update(Long id, BackupVO backup) { + return Transaction.execute((TransactionCallback) status -> { + boolean result = super.update(id, backup); + if (result) { + saveDetails(backup); + } + return result; + }); + } + @Override public List syncBackups(Long zoneId, Long vmId, List externalBackups) { for (Backup backup : externalBackups) { @@ -143,44 +232,62 @@ public List syncBackups(Long zoneId, Long vmId, List externalBac } @Override - public BackupResponse newBackupResponse(Backup backup) { - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(backup.getVmId()); - AccountVO account = accountDao.findByIdIncludingRemoved(vm.getAccountId()); - DomainVO domain = domainDao.findByIdIncludingRemoved(vm.getDomainId()); - DataCenterVO zone = dataCenterDao.findByIdIncludingRemoved(vm.getDataCenterId()); - Long offeringId = backup.getBackupOfferingId(); - if (offeringId == null) { - offeringId = vm.getBackupOfferingId(); + public Long countBackupsForAccount(long accountId) { + SearchCriteria sc = CountBackupsByAccount.create(); + sc.setParameters("account", accountId); + sc.setParameters("status", Backup.Status.Failed, Backup.Status.Removed, Backup.Status.Expunged); + return customSearch(sc, null).get(0); + } + + @Override + public Long calculateBackupStorageForAccount(long accountId) { + SearchCriteria sc = CalculateBackupStorageByAccount.create(); + sc.setParameters("account", accountId); + sc.setParameters("status", Backup.Status.Failed, Backup.Status.Removed, Backup.Status.Expunged); + return customSearch(sc, null).get(0).sum; + } + + @Override + public List listBySchedule(Long backupScheduleId) { + SearchCriteria sc = listBackupsBySchedule.create(); + sc.setParameters("backup_schedule_id", backupScheduleId); + sc.setParameters("status", Backup.Status.BackedUp); + return listBy(sc, new Filter(BackupVO.class, "date", true)); + } + + @Override + public void loadDetails(BackupVO backup) { + Map details = backupDetailsDao.listDetailsKeyPairs(backup.getId()); + backup.setDetails(details); + } + + @Override + public void saveDetails(BackupVO backup) { + Map detailsStr = backup.getDetails(); + if (detailsStr == null) { + return; } - BackupOffering offering = backupOfferingDao.findByIdIncludingRemoved(offeringId); - - BackupResponse response = new BackupResponse(); - response.setId(backup.getUuid()); - response.setVmId(vm.getUuid()); - response.setVmName(vm.getHostName()); - response.setExternalId(backup.getExternalId()); - response.setType(backup.getType()); - response.setDate(backup.getDate()); - response.setSize(backup.getSize()); - response.setProtectedSize(backup.getProtectedSize()); - response.setStatus(backup.getStatus()); - // ACS 4.20: For backups taken prior this release the backup.backed_volumes column would be empty hence use vm_instance.backup_volumes - String backedUpVolumes; - if (Objects.isNull(backup.getBackedUpVolumes())) { - backedUpVolumes = new Gson().toJson(vm.getBackupVolumeList().toArray(), Backup.VolumeInfo[].class); - } else { - backedUpVolumes = new Gson().toJson(backup.getBackedUpVolumes().toArray(), Backup.VolumeInfo[].class); + List details = new ArrayList(); + for (String key : detailsStr.keySet()) { + BackupDetailVO detail = new BackupDetailVO(backup.getId(), key, detailsStr.get(key), true); + details.add(detail); } - response.setVolumes(backedUpVolumes); - response.setBackupOfferingId(offering.getUuid()); - response.setBackupOffering(offering.getName()); - response.setAccountId(account.getUuid()); - response.setAccount(account.getAccountName()); - response.setDomainId(domain.getUuid()); - response.setDomain(domain.getName()); - response.setZoneId(zone.getUuid()); - response.setZone(zone.getName()); - response.setObjectName("backup"); - return response; + backupDetailsDao.saveDetails(details); + } + + @Override + public List listVmIdsWithBackupsInZone(Long zoneId) { + SearchCriteria sc = backupVmSearchInZone.create(); + sc.setParameters("zone_id", zoneId); + return customSearchIncludingRemoved(sc, null); + } + + @Override + public BackupVO findLatestByStatusAndVmId(Backup.Status status, long vmId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters("vm_id", vmId); + sc.setParameters("status", status); + Filter filter = new Filter(BackupVO.class, "date", false, 0L, 1L); + return findOneBy(sc, filter); } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDao.java new file mode 100644 index 000000000000..c6c8fc3a322e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDao.java @@ -0,0 +1,41 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import org.apache.cloudstack.backup.BackupDetailVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +import com.cloud.utils.db.GenericDao; + +public interface BackupDetailsDao extends GenericDao, ResourceDetailsDao { + + String END_OF_CHAIN = "end_of_chain"; + + String CURRENT = "current"; + + String IMAGE_STORE_ID = "image_store_id"; + + String PARENT_ID = "parent_id"; + + String ISOLATED = "isolated"; + + String SCREENSHOT_PATH = "screenshot_path"; + + String BACKUP_HASH = "backup_hash"; + + void removeDetailsExcept(long backupId, String exception); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDaoImpl.java new file mode 100644 index 000000000000..5f257c23892c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupDetailsDaoImpl.java @@ -0,0 +1,57 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + + +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.backup.BackupDetailVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; + +@Component +public class BackupDetailsDaoImpl extends ResourceDetailsDaoBase implements BackupDetailsDao { + + private SearchBuilder backupDetailSearch; + + private static final String BACKUP_ID = "backup_id"; + + private static final String KEY = "key"; + + @PostConstruct + protected void init() { + backupDetailSearch = createSearchBuilder(); + backupDetailSearch.and(BACKUP_ID, backupDetailSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + backupDetailSearch.and(KEY, backupDetailSearch.entity().getName(), SearchCriteria.Op.NEQ); + backupDetailSearch.done(); + } + + @Override + public void removeDetailsExcept(long backupId, String exception) { + SearchCriteria sc = backupDetailSearch.create(); + sc.setParameters(BACKUP_ID, backupId); + sc.setParameters(KEY, exception); + super.expunge(sc); + } + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new BackupDetailVO(resourceId, key, value, display)); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java index d001de8b6c67..d40e828121e0 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDao.java @@ -24,7 +24,7 @@ import com.cloud.utils.db.GenericDao; public interface BackupOfferingDao extends GenericDao { - BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy); + BackupOfferingResponse newBackupOfferingResponse(BackupOffering policy, Boolean crossZoneInstanceCreation); BackupOffering findByExternalId(String externalId, Long zoneId); BackupOffering findByName(String name, Long zoneId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java index 0568a0185bba..1d7a55d7caf7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDaoImpl.java @@ -20,8 +20,12 @@ import javax.annotation.PostConstruct; import javax.inject.Inject; +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.api.response.BackupOfferingResponse; import org.apache.cloudstack.backup.BackupOffering; +import org.apache.cloudstack.backup.BackupOfferingDetailsVO; import org.apache.cloudstack.backup.BackupOfferingVO; import com.cloud.dc.DataCenterVO; @@ -30,10 +34,18 @@ import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + public class BackupOfferingDaoImpl extends GenericDaoBase implements BackupOfferingDao { @Inject DataCenterDao dataCenterDao; + @Inject + BackupOfferingDetailsDao backupOfferingDetailsDao; + @Inject + DomainDao domainDao; private SearchBuilder backupPoliciesSearch; @@ -50,19 +62,45 @@ protected void init() { } @Override - public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering) { - DataCenterVO zone = dataCenterDao.findById(offering.getZoneId()); + public BackupOfferingResponse newBackupOfferingResponse(BackupOffering offering, Boolean crossZoneInstanceCreation) { + DataCenterVO zone = dataCenterDao.findById(offering.getZoneId()); + List domainIds = backupOfferingDetailsDao.findDomainIds(offering.getId()); + List details = backupOfferingDetailsDao.listDetails(offering.getId()); BackupOfferingResponse response = new BackupOfferingResponse(); response.setId(offering.getUuid()); response.setName(offering.getName()); response.setDescription(offering.getDescription()); response.setExternalId(offering.getExternalId()); + response.setProvider(offering.getProvider()); response.setUserDrivenBackups(offering.isUserDrivenBackupAllowed()); if (zone != null) { response.setZoneId(zone.getUuid()); response.setZoneName(zone.getName()); } + if (domainIds != null && !domainIds.isEmpty()) { + String domainUUIDs = domainIds.stream().map(Long::valueOf).map(domainId -> { + DomainVO domain = domainDao.findById(domainId); + return domain != null ? domain.getUuid() : ""; + }).filter(name -> !name.isEmpty()).reduce((a, b) -> a + "," + b).orElse(""); + String domainNames = domainIds.stream().map(Long::valueOf).map(domainId -> { + DomainVO domain = domainDao.findById(domainId); + return domain != null ? domain.getName() : ""; + }).filter(name -> !name.isEmpty()).reduce((a, b) -> a + "," + b).orElse(""); + response.setDomain(domainNames); + response.setDomainId(domainUUIDs); + } + if (crossZoneInstanceCreation) { + response.setCrossZoneInstanceCreation(true); + } + details.removeIf(backupOfferingDetailsVO -> ApiConstants.DOMAIN_ID.equals(backupOfferingDetailsVO.getName())); + Map detailString = new HashMap<>(); + for (BackupOfferingDetailsVO detail : details) { + detailString.put(detail.getName(), detail.getValue()); + } + if (!detailString.isEmpty()) { + response.setDetails(detailString); + } response.setCreated(offering.getCreated()); response.setObjectName("backupoffering"); return response; diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java new file mode 100644 index 000000000000..390fcba1e0e7 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDao.java @@ -0,0 +1,32 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import java.util.List; + +import org.apache.cloudstack.backup.BackupOfferingDetailsVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; + +import com.cloud.utils.db.GenericDao; + +public interface BackupOfferingDetailsDao extends GenericDao, ResourceDetailsDao { + List findDomainIds(final long resourceId); + List findZoneIds(final long resourceId); + String getDetail(Long backupOfferingId, String key); + List findOfferingIdsByDomainIds(List domainIds); + void updateBackupOfferingDomainIdsDetail(long backupOfferingId, List filteredDomainIds); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java new file mode 100644 index 000000000000..f052c93f9817 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupOfferingDetailsDaoImpl.java @@ -0,0 +1,101 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import com.cloud.utils.db.DB; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.backup.BackupOfferingDetailsVO; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.springframework.stereotype.Component; + +@Component +public class BackupOfferingDetailsDaoImpl extends ResourceDetailsDaoBase implements BackupOfferingDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new BackupOfferingDetailsVO(resourceId, key, value, display)); + } + + @Override + public List findDomainIds(long resourceId) { + final List domainIds = new ArrayList<>(); + for (final BackupOfferingDetailsVO detail: findDetails(resourceId, ApiConstants.DOMAIN_ID)) { + final Long domainId = Long.valueOf(detail.getValue()); + if (domainId > 0) { + domainIds.add(domainId); + } + } + return domainIds; + } + + @Override + public List findZoneIds(long resourceId) { + final List zoneIds = new ArrayList<>(); + for (final BackupOfferingDetailsVO detail: findDetails(resourceId, ApiConstants.ZONE_ID)) { + final Long zoneId = Long.valueOf(detail.getValue()); + if (zoneId > 0) { + zoneIds.add(zoneId); + } + } + return zoneIds; + } + + @Override + public String getDetail(Long backupOfferingId, String key) { + String detailValue = null; + BackupOfferingDetailsVO backupOfferingDetail = findDetail(backupOfferingId, key); + if (backupOfferingDetail != null) { + detailValue = backupOfferingDetail.getValue(); + } + return detailValue; + } + + @Override + public List findOfferingIdsByDomainIds(List domainIds) { + Object[] dIds = domainIds.stream().map(s -> String.valueOf(s)).collect(Collectors.toList()).toArray(); + return findResourceIdsByNameAndValueIn("domainid", dIds); + } + + @DB + @Override + public void updateBackupOfferingDomainIdsDetail(long backupOfferingId, List filteredDomainIds) { + SearchBuilder sb = createSearchBuilder(); + List detailsVO = new ArrayList<>(); + sb.and("offeringId", sb.entity().getResourceId(), SearchCriteria.Op.EQ); + sb.and("detailName", sb.entity().getName(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("offeringId", String.valueOf(backupOfferingId)); + sc.setParameters("detailName", ApiConstants.DOMAIN_ID); + remove(sc); + for (Long domainId : filteredDomainIds) { + detailsVO.add(new BackupOfferingDetailsVO(backupOfferingId, ApiConstants.DOMAIN_ID, String.valueOf(domainId), false)); + } + if (!detailsVO.isEmpty()) { + for (BackupOfferingDetailsVO detailVO : detailsVO) { + persist(detailVO); + } + } + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDao.java index 0034bfb30ab6..6dec994be0c9 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDao.java @@ -28,4 +28,6 @@ public interface BackupRepositoryDao extends GenericDao listByZoneAndProvider(Long zoneId, String provider); BackupRepository findByBackupOfferingId(Long backupOfferingId); + + boolean updateCapacity(BackupRepository backupRepository, Long capacityBytes, Long usedBytes); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java index 460b6d8aba45..ea969988e2bb 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupRepositoryDaoImpl.java @@ -64,4 +64,12 @@ public BackupRepository findByBackupOfferingId(Long backupOfferingId) { } return findByUuid(offering.getExternalId()); } + + @Override + public boolean updateCapacity(BackupRepository backupRepository, Long capacityBytes, Long usedBytes) { + BackupRepositoryVO repository = findById(backupRepository.getId()); + repository.setCapacityBytes(capacityBytes); + repository.setUsedBytes(usedBytes); + return update(repository.getId(), repository); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java index ee1783a9c896..87b7dab1ff7b 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDao.java @@ -21,20 +21,14 @@ import java.util.List; import com.cloud.utils.DateUtil; -import org.apache.cloudstack.api.response.BackupScheduleResponse; -import org.apache.cloudstack.backup.BackupSchedule; import org.apache.cloudstack.backup.BackupScheduleVO; import com.cloud.utils.db.GenericDao; public interface BackupScheduleDao extends GenericDao { - BackupScheduleVO findByVM(Long vmId); - List listByVM(Long vmId); BackupScheduleVO findByVMAndIntervalType(Long vmId, DateUtil.IntervalType intervalType); List getSchedulesToExecute(Date currentTimestamp); - - BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java index e00ccc5abd77..972af73391af 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/BackupScheduleDaoImpl.java @@ -17,28 +17,23 @@ package org.apache.cloudstack.backup.dao; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.Date; import java.util.List; import javax.annotation.PostConstruct; -import javax.inject.Inject; import com.cloud.utils.DateUtil; -import org.apache.cloudstack.api.response.BackupScheduleResponse; -import org.apache.cloudstack.backup.BackupSchedule; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.TransactionLegacy; import org.apache.cloudstack.backup.BackupScheduleVO; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import com.cloud.vm.VMInstanceVO; -import com.cloud.vm.dao.VMInstanceDao; public class BackupScheduleDaoImpl extends GenericDaoBase implements BackupScheduleDao { - - @Inject - VMInstanceDao vmInstanceDao; - private SearchBuilder backupScheduleSearch; private SearchBuilder executableSchedulesSearch; @@ -59,13 +54,6 @@ protected void init() { executableSchedulesSearch.done(); } - @Override - public BackupScheduleVO findByVM(Long vmId) { - SearchCriteria sc = backupScheduleSearch.create(); - sc.setParameters("vm_id", vmId); - return findOneBy(sc); - } - @Override public List listByVM(Long vmId) { SearchCriteria sc = backupScheduleSearch.create(); @@ -88,16 +76,19 @@ public List getSchedulesToExecute(Date currentTimestamp) { return listBy(sc); } + @DB @Override - public BackupScheduleResponse newBackupScheduleResponse(BackupSchedule schedule) { - VMInstanceVO vm = vmInstanceDao.findByIdIncludingRemoved(schedule.getVmId()); - BackupScheduleResponse response = new BackupScheduleResponse(); - response.setVmId(vm.getUuid()); - response.setVmName(vm.getHostName()); - response.setIntervalType(schedule.getScheduleType()); - response.setSchedule(schedule.getSchedule()); - response.setTimezone(schedule.getTimezone()); - response.setObjectName("backupschedule"); - return response; + public boolean remove(Long id) { + String sql = "UPDATE backups SET backup_schedule_id = NULL WHERE backup_schedule_id = ?"; + TransactionLegacy transaction = TransactionLegacy.currentTxn(); + try { + PreparedStatement preparedStatement = transaction.prepareAutoCloseStatement(sql); + preparedStatement.setLong(1, id); + preparedStatement.executeUpdate(); + return super.remove(id); + } catch (SQLException e) { + logger.warn("Unable to clean up backup schedules references from the backups table.", e); + return false; + } } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java new file mode 100644 index 000000000000..9a9fbeb8814e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDao.java @@ -0,0 +1,36 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup.dao; + +import java.util.List; + +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.ImageTransferVO; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDao; + +public interface ImageTransferDao extends GenericDao { + List listByBackupId(Long backupId); + ImageTransferVO findByUuid(String uuid); + ImageTransferVO findByVolume(Long volumeId); + ImageTransferVO findUnfinishedByVolume(Long volumeId); + List listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction); + List listByZonesAndOwners(List zoneIds, List accountIds, List domainIds, + Filter filter); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java new file mode 100644 index 000000000000..2e36f924e4ef --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/ImageTransferDaoImpl.java @@ -0,0 +1,136 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. + +package org.apache.cloudstack.backup.dao; + +import java.util.Collections; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.apache.cloudstack.backup.ImageTransfer; +import org.apache.cloudstack.backup.ImageTransferVO; +import org.apache.commons.collections.CollectionUtils; +import org.springframework.stereotype.Component; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +public class ImageTransferDaoImpl extends GenericDaoBase implements ImageTransferDao { + + private SearchBuilder backupIdSearch; + private SearchBuilder uuidSearch; + private SearchBuilder volumeSearch; + private SearchBuilder volumeUnfinishedSearch; + private SearchBuilder phaseDirectionSearch; + + public ImageTransferDaoImpl() { + } + + @PostConstruct + protected void init() { + backupIdSearch = createSearchBuilder(); + backupIdSearch.and("backupId", backupIdSearch.entity().getBackupId(), SearchCriteria.Op.EQ); + backupIdSearch.done(); + + uuidSearch = createSearchBuilder(); + uuidSearch.and("uuid", uuidSearch.entity().getUuid(), SearchCriteria.Op.EQ); + uuidSearch.done(); + + volumeSearch = createSearchBuilder(); + volumeSearch.and("volumeId", volumeSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + volumeSearch.done(); + + volumeUnfinishedSearch = createSearchBuilder(); + volumeUnfinishedSearch.and("volumeId", volumeUnfinishedSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + volumeUnfinishedSearch.and("phase", volumeUnfinishedSearch.entity().getPhase(), SearchCriteria.Op.NEQ); + volumeUnfinishedSearch.done(); + + phaseDirectionSearch = createSearchBuilder(); + phaseDirectionSearch.and("phase", phaseDirectionSearch.entity().getPhase(), SearchCriteria.Op.EQ); + phaseDirectionSearch.and("direction", phaseDirectionSearch.entity().getDirection(), SearchCriteria.Op.EQ); + phaseDirectionSearch.done(); + } + + @Override + public List listByBackupId(Long backupId) { + SearchCriteria sc = backupIdSearch.create(); + sc.setParameters("backupId", backupId); + return listBy(sc); + } + + @Override + public ImageTransferVO findByUuid(String uuid) { + SearchCriteria sc = uuidSearch.create(); + sc.setParameters("uuid", uuid); + return findOneBy(sc); + } + + @Override + public ImageTransferVO findByVolume(Long volumeId) { + SearchCriteria sc = volumeSearch.create(); + sc.setParameters("volumeId", volumeId); + return findOneBy(sc); + } + + @Override + public ImageTransferVO findUnfinishedByVolume(Long volumeId) { + SearchCriteria sc = volumeUnfinishedSearch.create(); + sc.setParameters("volumeId", volumeId); + sc.setParameters("phase", ImageTransferVO.Phase.finished.toString()); + return findOneBy(sc); + } + + @Override + public List listByPhaseAndDirection(ImageTransfer.Phase phase, ImageTransfer.Direction direction) { + SearchCriteria sc = phaseDirectionSearch.create(); + sc.setParameters("phase", phase); + sc.setParameters("direction", direction); + return listBy(sc); + } + + @Override + public List listByZonesAndOwners(List zoneIds, List accountIds, List domainIds, + Filter filter) { + if (CollectionUtils.isEmpty(zoneIds)) { + return Collections.emptyList(); + } + SearchBuilder sb = createSearchBuilder(); + sb.and("dataCenterId", sb.entity().getDataCenterId(), SearchCriteria.Op.IN); + boolean accountIdsNotEmpty = CollectionUtils.isNotEmpty(accountIds); + boolean domainIdsNotEmpty = CollectionUtils.isNotEmpty(domainIds); + if (accountIdsNotEmpty || domainIdsNotEmpty) { + sb.and().op("account", sb.entity().getAccountId(), SearchCriteria.Op.IN); + sb.or("domain", sb.entity().getDomainId(), SearchCriteria.Op.IN); + sb.cp(); + } + sb.done(); + final SearchCriteria sc = sb.create(); + sc.setParameters("dataCenterId", zoneIds.toArray()); + if (accountIdsNotEmpty) { + sc.setParameters("account", accountIds.toArray()); + } + if (domainIdsNotEmpty) { + sc.setParameters("domain", domainIds.toArray()); + } + + return listBy(sc, filter); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupDataStoreDao.java new file mode 100644 index 000000000000..e71ffe5ebde9 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupDataStoreDao.java @@ -0,0 +1,33 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.InternalBackupDataStoreVO; + +import java.util.List; + +public interface InternalBackupDataStoreDao extends GenericDao { + + List listByBackupId(long backupId); + + InternalBackupDataStoreVO findByBackupIdAndVolumeId(long backupId, long volumeId); + + void expungeByBackupId(long backupId); + + void updateVolumeId(long oldVolumeId, long newVolumeId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupDataStoreDaoImpl.java new file mode 100644 index 000000000000..34a064ff62a2 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupDataStoreDaoImpl.java @@ -0,0 +1,74 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.UpdateBuilder; +import org.apache.cloudstack.backup.InternalBackupDataStoreVO; + +import javax.annotation.PostConstruct; +import java.util.List; + +public class InternalBackupDataStoreDaoImpl extends GenericDaoBase implements InternalBackupDataStoreDao { + + private SearchBuilder backupSearch; + + private static final String BACKUP_ID = "backup_id"; + private static final String VOLUME_ID = "volume_id"; + + @PostConstruct + protected void init() { + backupSearch = createSearchBuilder(); + backupSearch.and(BACKUP_ID, backupSearch.entity().getBackupId(), SearchCriteria.Op.EQ); + backupSearch.and(VOLUME_ID, backupSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + backupSearch.done(); + } + + @Override + public List listByBackupId(long backupId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(BACKUP_ID, backupId); + return listBy(sc); + } + + @Override + public InternalBackupDataStoreVO findByBackupIdAndVolumeId(long backupId, long volumeId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(BACKUP_ID, backupId); + sc.setParameters(VOLUME_ID, volumeId); + return findOneBy(sc); + } + + @Override + public void expungeByBackupId(long backupId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(BACKUP_ID, backupId); + expunge(sc); + } + + @Override + public void updateVolumeId(long oldVolumeId, long newVolumeId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VOLUME_ID, oldVolumeId); + InternalBackupDataStoreVO delta = createForUpdate(); + delta.setVolumeId(newVolumeId); + UpdateBuilder ub = getUpdateBuilder(delta); + update(ub, sc, null); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupJoinDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupJoinDao.java new file mode 100644 index 000000000000..3cb6ea241041 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupJoinDao.java @@ -0,0 +1,44 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.InternalBackupJoinVO; + +import java.util.Date; +import java.util.List; + +public interface InternalBackupJoinDao extends GenericDao { + + List listByBackedUpAndVmIdAndDateBeforeOrAfterOrderBy(long vmId, Long scheduleId, Date date, boolean before, boolean ascending); + + List listIncludingRemovedByVmIdAndBeforeDateOrderByCreatedDesc(long vmId, Long scheduleId, Date beforeDate); + + InternalBackupJoinVO findCurrent(long vmId, Long scheduleId); + + List listCurrents(long vmId, boolean descending); + + List listCurrentsByVolumeIdDesc(long volumeId); + + InternalBackupJoinVO findByParentId(long parentId); + + List listByImageStoreId(long imageStoreId); + + List listById(long id); + + List listByParentId(long parentId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupJoinDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupJoinDaoImpl.java new file mode 100644 index 000000000000..bbc91db13e83 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupJoinDaoImpl.java @@ -0,0 +1,158 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.backup.Backup; +import org.apache.cloudstack.backup.InternalBackupJoinVO; + +import javax.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class InternalBackupJoinDaoImpl extends GenericDaoBase implements InternalBackupJoinDao { + + private static final String ID = "id"; + private static final String VM_ID = "vm_id"; + private static final String STATUS = "status"; + private static final String CREATED_BEFORE = "created_before"; + private static final String CREATED_AFTER = "created_after"; + private static final String CURRENT = "current"; + private static final String ISOLATED = "isolated"; + private static final String PARENT_ID = "parent_id"; + private static final String IMAGE_STORE_ID = "image_store_id"; + private static final String SCHEDULE_ID = "schedule_id"; + private static final String VOLUME_ID = "volume_id"; + private SearchBuilder backupSearch; + private SearchBuilder allBackupsSearch; + + @PostConstruct + protected void init() { + backupSearch = createSearchBuilder(); + backupSearch.and(VM_ID, backupSearch.entity().getVmId(), SearchCriteria.Op.EQ); + backupSearch.and(STATUS, backupSearch.entity().getStatus(), SearchCriteria.Op.IN); + backupSearch.and(CREATED_BEFORE, backupSearch.entity().getDate(), SearchCriteria.Op.LT); + backupSearch.and(CREATED_AFTER, backupSearch.entity().getDate(), SearchCriteria.Op.GT); + backupSearch.and(CURRENT, backupSearch.entity().getCurrent(), SearchCriteria.Op.EQ); + backupSearch.and(PARENT_ID, backupSearch.entity().getParentId(), SearchCriteria.Op.EQ); + backupSearch.and(ISOLATED, backupSearch.entity().getIsolated(), SearchCriteria.Op.EQ); + backupSearch.and(SCHEDULE_ID, backupSearch.entity().getScheduleId(), SearchCriteria.Op.EQ); + backupSearch.groupBy(backupSearch.entity().getId()); + backupSearch.done(); + + allBackupsSearch = createSearchBuilder(); + allBackupsSearch.and(ID, allBackupsSearch.entity().getId(), SearchCriteria.Op.EQ); + allBackupsSearch.and(STATUS, allBackupsSearch.entity().getStatus(), SearchCriteria.Op.IN); + allBackupsSearch.and(PARENT_ID, allBackupsSearch.entity().getParentId(), SearchCriteria.Op.EQ); + allBackupsSearch.and(IMAGE_STORE_ID, allBackupsSearch.entity().getImageStoreId(), SearchCriteria.Op.EQ); + allBackupsSearch.and(VM_ID, allBackupsSearch.entity().getVmId(), SearchCriteria.Op.EQ); + allBackupsSearch.and(VOLUME_ID, allBackupsSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + allBackupsSearch.and(CURRENT, allBackupsSearch.entity().getCurrent(), SearchCriteria.Op.EQ); + allBackupsSearch.done(); + } + + @Override + public List listByBackedUpAndVmIdAndDateBeforeOrAfterOrderBy(long vmId, Long scheduleId, Date date, boolean before, boolean ascending) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VM_ID, vmId); + sc.setParameters(STATUS, Backup.Status.BackedUp); + if (before) { + sc.setParameters(CREATED_BEFORE, date); + } else { + sc.setParameters(CREATED_AFTER, date); + } + sc.setParameters(ISOLATED, Boolean.FALSE.toString()); + sc.setParameters(SCHEDULE_ID, scheduleId); + Filter filter = new Filter(InternalBackupJoinVO.class, "date", ascending); + return new ArrayList<>(listBy(sc, filter)); + } + + @Override + public List listIncludingRemovedByVmIdAndBeforeDateOrderByCreatedDesc(long vmId, Long scheduleId, Date beforeDate) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VM_ID, vmId); + sc.setParameters(STATUS, Backup.Status.BackedUp, Backup.Status.Removed); + sc.setParameters(CREATED_BEFORE, beforeDate); + sc.setParameters(ISOLATED, Boolean.FALSE.toString()); + sc.setParameters(SCHEDULE_ID, scheduleId); + Filter filter = new Filter(InternalBackupJoinVO.class, "date", false); + return new ArrayList<>(listIncludingRemovedBy(sc, filter)); + } + + @Override + public InternalBackupJoinVO findCurrent(long vmId, Long scheduleId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VM_ID, vmId); + sc.setParameters(CURRENT, Boolean.TRUE.toString()); + sc.setParameters(SCHEDULE_ID, scheduleId); + return findOneBy(sc); + } + + @Override + public InternalBackupJoinVO findByParentId(long parentId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(PARENT_ID, parentId); + return findOneIncludingRemovedBy(sc); + } + + @Override + public List listByImageStoreId(long imageStoreId) { + SearchCriteria sc = allBackupsSearch.create(); + sc.setParameters(IMAGE_STORE_ID, imageStoreId); + sc.setParameters(STATUS, Backup.Status.BackedUp); + return listBy(sc); + } + + @Override + public List listById(long id) { + SearchCriteria sc = allBackupsSearch.create(); + sc.setParameters(ID, id); + sc.setParameters(STATUS, Backup.Status.BackedUp); + return listBy(sc); + } + + @Override + public List listByParentId(long parentId) { + SearchCriteria sc = allBackupsSearch.create(); + sc.setParameters(PARENT_ID, parentId); + sc.setParameters(STATUS, Backup.Status.BackedUp); + return listBy(sc); + } + + @Override + public List listCurrents(long vmId, boolean descending) { + SearchCriteria sc = allBackupsSearch.create(); + sc.setParameters(VM_ID, vmId); + sc.setParameters(CURRENT, Boolean.TRUE.toString()); + Filter filter = new Filter(InternalBackupJoinVO.class, "date", !descending); + + return listBy(sc, filter); + } + + @Override + public List listCurrentsByVolumeIdDesc(long volumeId) { + SearchCriteria sc = allBackupsSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(CURRENT, Boolean.TRUE.toString()); + Filter filter = new Filter(InternalBackupJoinVO.class, "date", false); + return listBy(sc, filter); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupServiceJobDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupServiceJobDao.java new file mode 100644 index 000000000000..c80734b9cb3c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupServiceJobDao.java @@ -0,0 +1,39 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.InternalBackupServiceJobType; +import org.apache.cloudstack.backup.InternalBackupServiceJobVO; + +import java.util.Date; +import java.util.List; + +public interface InternalBackupServiceJobDao extends GenericDao { + + List listExecutingJobsByZoneIdAndJobType(long zoneId, InternalBackupServiceJobType... jobTypes); + + List listWaitingJobsAndScheduledToBeforeNow(long zoneId, InternalBackupServiceJobType... jobTypes); + + List listExecutingJobsByHostsAndStartTimeBeforeAndTypeIn(Object[] hostIds, Date date, InternalBackupServiceJobType... jobTypes); + + Pair, Integer> searchAndCountForListApi(Long id, Long backupId, Long hostId, Long zoneId, InternalBackupServiceJobType type, boolean executing, + boolean scheduled, Long startIndex, Long pageSize); + + void update(InternalBackupServiceJobVO job); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupServiceJobDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupServiceJobDaoImpl.java new file mode 100644 index 000000000000..32e81261838d --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupServiceJobDaoImpl.java @@ -0,0 +1,138 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.DateUtil; +import com.cloud.utils.Pair; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.backup.InternalBackupServiceJobType; +import org.apache.cloudstack.backup.InternalBackupServiceJobVO; + +import javax.annotation.PostConstruct; +import java.util.Date; +import java.util.List; + +public class InternalBackupServiceJobDaoImpl extends GenericDaoBase implements InternalBackupServiceJobDao { + private static final String ID = "id"; + private static final String BACKUP_ID = "backup_id"; + private SearchBuilder executingBeforeAndHostInAndTypeInSearch; + private SearchBuilder scheduledAndNotStartedSearch; + + private SearchBuilder executingAndZoneIdAndTypeSearch; + + private static final String HOST_ID = "host_id"; + private static final String TYPE = "type"; + private static final String START_TIME = "start_time"; + private static final String SCHEDULED = "scheduled"; + private static final String ZONE_ID = "zone_id"; + + @PostConstruct + protected void init() { + executingBeforeAndHostInAndTypeInSearch = createSearchBuilder(); + executingBeforeAndHostInAndTypeInSearch.and(HOST_ID, executingBeforeAndHostInAndTypeInSearch.entity().getHostId(), SearchCriteria.Op.IN); + executingBeforeAndHostInAndTypeInSearch.and(START_TIME, executingBeforeAndHostInAndTypeInSearch.entity().getStartTime(), SearchCriteria.Op.LTEQ); + executingBeforeAndHostInAndTypeInSearch.and(TYPE, executingBeforeAndHostInAndTypeInSearch.entity().getType(), SearchCriteria.Op.IN); + executingBeforeAndHostInAndTypeInSearch.done(); + + scheduledAndNotStartedSearch = createSearchBuilder(); + scheduledAndNotStartedSearch.and(SCHEDULED, scheduledAndNotStartedSearch.entity().getScheduledStartTime(), SearchCriteria.Op.LTEQ); + scheduledAndNotStartedSearch.and(START_TIME, scheduledAndNotStartedSearch.entity().getStartTime(), SearchCriteria.Op.NULL); + scheduledAndNotStartedSearch.and(ZONE_ID, scheduledAndNotStartedSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + scheduledAndNotStartedSearch.and(TYPE, scheduledAndNotStartedSearch.entity().getType(), SearchCriteria.Op.IN); + scheduledAndNotStartedSearch.done(); + + executingAndZoneIdAndTypeSearch = createSearchBuilder(); + executingAndZoneIdAndTypeSearch.and(START_TIME, executingAndZoneIdAndTypeSearch.entity().getStartTime(), SearchCriteria.Op.NNULL); + executingAndZoneIdAndTypeSearch.and(ZONE_ID, executingAndZoneIdAndTypeSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + executingAndZoneIdAndTypeSearch.and(TYPE, executingAndZoneIdAndTypeSearch.entity().getType(), SearchCriteria.Op.IN); + executingAndZoneIdAndTypeSearch.done(); + } + + @Override + public List listExecutingJobsByZoneIdAndJobType(long zoneId, InternalBackupServiceJobType... jobTypes) { + SearchCriteria sc = executingAndZoneIdAndTypeSearch.create(); + sc.setParameters(TYPE, (Object[]) jobTypes); + sc.setParameters(ZONE_ID, zoneId); + + return listBy(sc); + } + + @Override + public List listWaitingJobsAndScheduledToBeforeNow(long zoneId, InternalBackupServiceJobType... jobTypes) { + SearchCriteria sc = scheduledAndNotStartedSearch.create(); + + sc.setParameters(SCHEDULED, DateUtil.now()); + sc.setParameters(ZONE_ID, zoneId); + sc.setParameters(TYPE, (Object[]) jobTypes); + + Filter filter = new Filter(InternalBackupServiceJobVO.class, "scheduledStartTime", true); + return listBy(sc, filter); + } + + @Override + public List listExecutingJobsByHostsAndStartTimeBeforeAndTypeIn(Object[] hostIds, Date date, InternalBackupServiceJobType... jobTypes) { + SearchCriteria sc = executingBeforeAndHostInAndTypeInSearch.create(); + sc.setParameters(HOST_ID, hostIds); + sc.setParameters(START_TIME, date); + sc.setParameters(TYPE, (Object[]) jobTypes); + + return listBy(sc); + } + + @Override + public Pair, Integer> searchAndCountForListApi(Long id, Long backupId, Long hostId, Long zoneId, InternalBackupServiceJobType type, boolean executing, + boolean scheduled, Long startIndex, Long pageSize) { + SearchBuilder sb = createSearchBuilder(); + + sb.and(ID, sb.entity().getId(), SearchCriteria.Op.EQ); + sb.and(BACKUP_ID, sb.entity().getBackupId(), SearchCriteria.Op.EQ); + sb.and(HOST_ID, sb.entity().getHostId(), SearchCriteria.Op.EQ); + sb.and(ZONE_ID, sb.entity().getZoneId(), SearchCriteria.Op.EQ); + sb.and(TYPE, sb.entity().getType(), SearchCriteria.Op.EQ); + + boolean removed = !executing && !scheduled; + if (executing && !scheduled) { + sb.and("executing", sb.entity().getStartTime(), SearchCriteria.Op.NNULL); + } else if (scheduled && !executing) { + sb.and("scheduled", sb.entity().getStartTime(), SearchCriteria.Op.NULL); + } + + SearchCriteria sc = sb.create(); + + sc.setParametersIfNotNull(ID, id); + sc.setParametersIfNotNull(BACKUP_ID, backupId); + sc.setParametersIfNotNull(HOST_ID, hostId); + sc.setParametersIfNotNull(ZONE_ID, zoneId); + if (type != null) { + sc.setParameters(TYPE, type); + } + + Filter filter = new Filter(InternalBackupServiceJobVO.class, "created", false, startIndex, pageSize); + + return searchAndCount(sc, filter, removed); + } + + @Override + @DB + public void update(InternalBackupServiceJobVO job) { + super.update(job.getId(), job); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupStoragePoolDao.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupStoragePoolDao.java new file mode 100644 index 000000000000..e6628f78af8c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupStoragePoolDao.java @@ -0,0 +1,37 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.backup.InternalBackupStoragePoolVO; + +import java.util.List; + +public interface InternalBackupStoragePoolDao extends GenericDao { + + List listByBackupId(long backupId); + + List listByVolumeId(long volumeId); + + InternalBackupStoragePoolVO findOneByVolumeIdAndBackupId(long volumeId, long backupId); + + void expungeByBackupId(long backupId); + + void expungeByVolumeId(long volumeId); + + void expungeByVolumeIdAndBackupId(long volumeId, long backupId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupStoragePoolDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupStoragePoolDaoImpl.java new file mode 100644 index 000000000000..41002aeb51d2 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/backup/dao/InternalBackupStoragePoolDaoImpl.java @@ -0,0 +1,86 @@ +//Licensed to the Apache Software Foundation (ASF) under one +//or more contributor license agreements. See the NOTICE file +//distributed with this work for additional information +//regarding copyright ownership. The ASF licenses this file +//to you under the Apache License, Version 2.0 (the +//"License"); you may not use this file except in compliance +//the License. You may obtain a copy of the License at +// +//http://www.apache.org/licenses/LICENSE-2.0 +// +//Unless required by applicable law or agreed to in writing, +//software distributed under the License is distributed on an +//"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +//KIND, either express or implied. See the License for the +//specific language governing permissions and limitations +//under the License. +package org.apache.cloudstack.backup.dao; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.backup.InternalBackupStoragePoolVO; + +import javax.annotation.PostConstruct; +import java.util.List; + +public class InternalBackupStoragePoolDaoImpl extends GenericDaoBase implements InternalBackupStoragePoolDao { + + private SearchBuilder backupSearch; + + private static final String BACKUP_ID = "backup_id"; + + private static final String VOLUME_ID = "volume_id"; + + @PostConstruct + protected void init() { + backupSearch = createSearchBuilder(); + backupSearch.and(BACKUP_ID, backupSearch.entity().getBackupId(), SearchCriteria.Op.EQ); + backupSearch.and(VOLUME_ID, backupSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); + backupSearch.done(); + } + + @Override + public List listByBackupId(long backupId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(BACKUP_ID, backupId); + return listBy(sc); + } + + @Override + public List listByVolumeId(long volumeId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + return listBy(sc); + } + + @Override + public InternalBackupStoragePoolVO findOneByVolumeIdAndBackupId(long volumeId, long backupId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(BACKUP_ID, backupId); + return findOneBy(sc); + } + + @Override + public void expungeByBackupId(long backupId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(BACKUP_ID, backupId); + expunge(sc); + } + + @Override + public void expungeByVolumeId(long volumeId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + expunge(sc); + } + + @Override + public void expungeByVolumeIdAndBackupId(long volumeId, long backupId) { + SearchCriteria sc = backupSearch.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(BACKUP_ID, backupId); + expunge(sc); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java b/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java new file mode 100644 index 000000000000..150c9662ada1 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/command/ReconcileCommandVO.java @@ -0,0 +1,216 @@ +// 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.command; + +import java.util.Date; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; + +import com.cloud.agent.api.Command; +import com.cloud.utils.db.GenericDao; + +import org.apache.cloudstack.acl.InfrastructureEntity; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.InternalIdentity; + +@Entity +@Table(name = "reconcile_commands") +public class ReconcileCommandVO implements InfrastructureEntity, InternalIdentity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "management_server_id") + private long managementServerId; + + @Column(name = "host_id") + private long hostId; + + @Column(name = "request_sequence") + private long requestSequence; + + @Column(name = "resource_id") + private long resourceId; + + @Column(name = "resource_type") + private ApiCommandResourceType resourceType; + + @Column(name = "state_by_management") + private Command.State stateByManagement; + + @Column(name = "state_by_agent") + private Command.State stateByAgent; + + @Column(name = "command_name") + private String commandName; + + @Column(name = "command_info", length = 65535) + private String commandInfo; + + @Column(name = "answer_name") + private String answerName; + + @Column(name = "answer_info", length = 65535) + private String answerInfo; + + @Column(name = GenericDao.CREATED_COLUMN) + private Date created; + + @Column(name= GenericDao.REMOVED_COLUMN) + private Date removed; + + @Temporal(TemporalType.TIMESTAMP) + @Column(name= "updated") + private Date updated; + + @Column(name= "retry_count") + private Long retryCount = 0L; + + @Override + public long getId() { + return id; + } + + public long getManagementServerId() { + return managementServerId; + } + + public void setManagementServerId(long managementServerId) { + this.managementServerId = managementServerId; + } + + public long getHostId() { + return hostId; + } + + public void setHostId(long hostId) { + this.hostId = hostId; + } + + public long getRequestSequence() { + return requestSequence; + } + + public void setRequestSequence(long requestSequence) { + this.requestSequence = requestSequence; + } + + public long getResourceId() { + return resourceId; + } + + public void setResourceId(long resourceId) { + this.resourceId = resourceId; + } + + public ApiCommandResourceType getResourceType() { + return resourceType; + } + + public void setResourceType(ApiCommandResourceType resourceType) { + this.resourceType = resourceType; + } + + public Command.State getStateByManagement() { + return stateByManagement; + } + + public void setStateByManagement(Command.State stateByManagement) { + this.stateByManagement = stateByManagement; + } + + public Command.State getStateByAgent() { + return stateByAgent; + } + + public void setStateByAgent(Command.State stateByAgent) { + this.stateByAgent = stateByAgent; + } + + public String getCommandName() { + return commandName; + } + + public void setCommandName(String commandName) { + this.commandName = commandName; + } + + public String getCommandInfo() { + return commandInfo; + } + + public void setCommandInfo(String commandInfo) { + this.commandInfo = commandInfo; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public String getAnswerName() { + return answerName; + } + + public void setAnswerName(String answerName) { + this.answerName = answerName; + } + + public String getAnswerInfo() { + return answerInfo; + } + + public void setAnswerInfo(String answerInfo) { + this.answerInfo = answerInfo; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public Date getUpdated() { + return updated; + } + + public void setUpdated(Date updated) { + this.updated = updated; + } + + public Long getRetryCount() { + return retryCount; + } + + public void setRetryCount(Long retryCount) { + this.retryCount = retryCount; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/command/dao/ReconcileCommandDao.java b/engine/schema/src/main/java/org/apache/cloudstack/command/dao/ReconcileCommandDao.java new file mode 100644 index 000000000000..59c4605d5487 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/command/dao/ReconcileCommandDao.java @@ -0,0 +1,45 @@ +// 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.command.dao; + +import com.cloud.agent.api.Command; +import com.cloud.utils.db.GenericDao; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.command.ReconcileCommandVO; + +import java.util.List; + +public interface ReconcileCommandDao extends GenericDao { + + List listByManagementServerId(long managementServerId); + + List listByHostId(long hostId); + + List listByState(Command.State... states); + + void removeCommand(long commandId, String commandName, Command.State state); + + ReconcileCommandVO findCommand(long reqSequence, String commandName); + + void updateCommandsToInterruptedByManagementServerId(long managementServerId); + + void updateCommandsToInterruptedByHostId(long hostId); + + List listByResourceIdAndTypeAndStates(long resourceId, ApiCommandResourceType resourceType, Command.State... states); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/command/dao/ReconcileCommandDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/command/dao/ReconcileCommandDaoImpl.java new file mode 100644 index 000000000000..593f464f9ad4 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/command/dao/ReconcileCommandDaoImpl.java @@ -0,0 +1,134 @@ +// 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.command.dao; + +import java.util.Date; +import java.util.List; + +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.command.ReconcileCommandVO; +import org.springframework.stereotype.Component; + +import com.cloud.agent.api.Command; +import com.cloud.utils.db.DB; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.QueryBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; + +@Component +@DB +public class ReconcileCommandDaoImpl extends GenericDaoBase implements ReconcileCommandDao { + + final SearchBuilder updateCommandSearch; + final SearchBuilder resourceSearch; + + public ReconcileCommandDaoImpl() { + + updateCommandSearch = createSearchBuilder(); + updateCommandSearch.and("managementServerId", updateCommandSearch.entity().getManagementServerId(), SearchCriteria.Op.EQ); + updateCommandSearch.and("stateByManagement", updateCommandSearch.entity().getStateByManagement(), SearchCriteria.Op.IN); + updateCommandSearch.and("hostId", updateCommandSearch.entity().getHostId(), SearchCriteria.Op.EQ); + updateCommandSearch.and("stateByAgent", updateCommandSearch.entity().getStateByAgent(), SearchCriteria.Op.IN); + updateCommandSearch.and("reqSequence", updateCommandSearch.entity().getRequestSequence(), SearchCriteria.Op.EQ); + updateCommandSearch.and("commandName", updateCommandSearch.entity().getCommandName(), SearchCriteria.Op.EQ); + updateCommandSearch.done(); + + resourceSearch = createSearchBuilder(); + resourceSearch.and("resourceId", resourceSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + resourceSearch.and("resourceType", resourceSearch.entity().getResourceType(), SearchCriteria.Op.EQ); + resourceSearch.and("stateByManagement", resourceSearch.entity().getStateByManagement(), SearchCriteria.Op.IN); + resourceSearch.done(); + } + + @Override + public List listByManagementServerId(long managementServerId) { + QueryBuilder sc = QueryBuilder.create(ReconcileCommandVO.class); + sc.and(sc.entity().getManagementServerId(), SearchCriteria.Op.EQ, managementServerId); + return sc.list(); + } + + @Override + public List listByHostId(long hostId) { + QueryBuilder sc = QueryBuilder.create(ReconcileCommandVO.class); + sc.and(sc.entity().getHostId(), SearchCriteria.Op.EQ, hostId); + return sc.list(); + } + + @Override + public List listByState(Command.State... states) { + QueryBuilder sc = QueryBuilder.create(ReconcileCommandVO.class); + sc.and(sc.entity().getStateByManagement(), SearchCriteria.Op.IN, (Object[]) states); + return sc.list(); + } + + @Override + public void removeCommand(long reqSequence, String commandName, Command.State state) { + SearchCriteria sc = updateCommandSearch.create(); + sc.setParameters("reqSequence", reqSequence); + sc.setParameters("commandName", commandName); + + ReconcileCommandVO vo = createForUpdate(); + if (state != null) { + vo.setStateByManagement(state); + } + vo.setRemoved(new Date()); + update(vo, sc); + } + + @Override + public ReconcileCommandVO findCommand(long reqSequence, String commandName) { + SearchCriteria sc = updateCommandSearch.create(); + sc.setParameters("reqSequence", reqSequence); + sc.setParameters("commandName", commandName); + return findOneBy(sc); + } + + @Override + public void updateCommandsToInterruptedByManagementServerId(long managementServerId) { + SearchCriteria sc = updateCommandSearch.create(); + sc.setParameters("managementServerId", managementServerId); + sc.setParameters("stateByManagement", Command.State.CREATED, Command.State.RECONCILING); + + ReconcileCommandVO vo = createForUpdate(); + vo.setStateByManagement(Command.State.INTERRUPTED); + + update(vo, sc); + } + + @Override + public void updateCommandsToInterruptedByHostId(long hostId) { + SearchCriteria sc = updateCommandSearch.create(); + sc.setParameters("hostId", hostId); + sc.setParameters("stateByAgent", Command.State.STARTED, Command.State.PROCESSING, Command.State.PROCESSING_IN_BACKEND); + + ReconcileCommandVO vo = createForUpdate(); + vo.setStateByAgent(Command.State.INTERRUPTED); + + update(vo, sc); + } + + @Override + public List listByResourceIdAndTypeAndStates(long resourceId, ApiCommandResourceType resourceType, Command.State... states) { + QueryBuilder sc = QueryBuilder.create(ReconcileCommandVO.class); + sc.and(sc.entity().getResourceId(), SearchCriteria.Op.EQ, resourceId); + sc.and(sc.entity().getResourceType(), SearchCriteria.Op.EQ, resourceType); + sc.and(sc.entity().getStateByManagement(), SearchCriteria.Op.IN, (Object[]) states); + return sc.list(); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.java new file mode 100644 index 000000000000..046a19c59fa0 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeDetailsVO.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.gui.theme; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "gui_themes_details") +public class GuiThemeDetailsVO implements GuiThemeDetails { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "gui_theme_id", nullable = false) + private Long guiThemeId; + + @Column(name = "type", nullable = false, length = 100) + private String type; + + @Column(name = "value", nullable = false, length = 65535) + private String value; + + public GuiThemeDetailsVO() { + } + + public GuiThemeDetailsVO(Long guiThemeId, String type, String value) { + this.guiThemeId = guiThemeId; + this.type = type; + this.value = value; + } + + @Override + public long getId() { + return id; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public Long getGuiThemeId() { + return guiThemeId; + } + + @Override + public void setGuiThemeId(Long guiThemeId) { + this.guiThemeId = guiThemeId; + } + + @Override + public String getType() { + return type; + } + + @Override + public void setType(String type) { + this.type = type; + } + + @Override + public String getValue() { + return value; + } + + @Override + public void setValue(String value) { + this.value = value; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java new file mode 100644 index 000000000000..45bad5a44d79 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeJoinVO.java @@ -0,0 +1,149 @@ +// 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.gui.theme; + +import com.cloud.utils.db.GenericDao; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; + +@Entity +@Table(name = "gui_themes_view") +public class GuiThemeJoinVO implements GuiThemeJoin { + @Id + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid; + + @Column(name = "name", nullable = false, length = 2048) + private String name; + + @Column(name = "description", length = 4096) + private String description; + + @Column(name = "css", nullable = false, length = 65535) + private String css; + + @Column(name = "json_configuration", nullable = false, length = 65535) + private String jsonConfiguration; + + @Column(name = "common_names", length = 65535) + private String commonNames; + + @Column(name = "domains", length = 65535) + private String domains; + + @Column(name = "accounts", length = 65535) + private String accounts; + + @Column(name = "recursive_domains") + private boolean recursiveDomains; + + @Column(name = "is_public") + private boolean isPublic; + + @Column(name = "login_base_domain") + private String loginBaseDomain; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed = null; + + public GuiThemeJoinVO() { + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getCss() { + return css; + } + + @Override + public String getJsonConfiguration() { + return jsonConfiguration; + } + + @Override + public String getCommonNames() { + return commonNames; + } + + @Override + public String getDomains() { + return domains; + } + + @Override + public String getAccounts() { + return accounts; + } + + @Override + public boolean isRecursiveDomains() { + return recursiveDomains; + } + + @Override + public boolean getIsPublic() { + return isPublic; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public String getLoginBaseDomain() { + return loginBaseDomain; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java new file mode 100644 index 000000000000..729071f60b80 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/GuiThemeVO.java @@ -0,0 +1,198 @@ +// 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.gui.theme; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "gui_themes") +public class GuiThemeVO implements GuiTheme { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid = UUID.randomUUID().toString(); + + @Column(name = "name", nullable = false, length = 2048) + private String name; + + @Column(name = "description", length = 4096) + private String description; + + @Column(name = "css", length = 65535) + private String css; + + @Column(name = "json_configuration", length = 65535) + private String jsonConfiguration; + + @Column(name = "is_public") + private boolean isPublic; + + @Column(name = "recursive_domains") + private boolean recursiveDomains = false; + + @Column(name = "login_base_domain", length = 65535) + private String loginBaseDomain; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(value = TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed = null; + + public GuiThemeVO() { + + } + + public GuiThemeVO(String name, String description, String css, String jsonConfiguration, boolean recursiveDomains, + boolean isPublic, Date created, String loginBaseDomain, Date removed) { + this.name = name; + this.description = description; + this.css = css; + this.jsonConfiguration = jsonConfiguration; + this.recursiveDomains = recursiveDomains; + this.isPublic = isPublic; + this.created = created; + this.loginBaseDomain = loginBaseDomain; + this.removed = removed; + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getCss() { + return css; + } + + @Override + public String getJsonConfiguration() { + return jsonConfiguration; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public boolean getIsPublic() { + return isPublic; + } + + @Override + public void setId(Long id) { + this.id = id; + } + + @Override + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public void setName(String name) { + this.name = name; + } + + @Override + public void setDescription(String description) { + this.description = description; + } + + @Override + public void setCss(String css) { + this.css = css; + } + + @Override + public void setJsonConfiguration(String jsonConfiguration) { + this.jsonConfiguration = jsonConfiguration; + } + + @Override + public void setCreated(Date created) { + this.created = created; + } + + @Override + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public void setIsPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + @Override + public boolean isRecursiveDomains() { + return recursiveDomains; + } + + @Override + public void setRecursiveDomains(boolean recursiveDomains) { + this.recursiveDomains = recursiveDomains; + } + + @Override + public String toString() { + return ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "uuid", "name", "description", "isPublic", "recursiveDomains"); + } + + public void setLoginBaseDomain(String loginBaseDomain) { + this.loginBaseDomain = loginBaseDomain; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDao.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDao.java new file mode 100644 index 000000000000..c00aedcc7866 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDao.java @@ -0,0 +1,24 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gui.theme.GuiThemeVO; + +public interface GuiThemeDao extends GenericDao { + +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDaoImpl.java new file mode 100644 index 000000000000..bc58c5f80f30 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDaoImpl.java @@ -0,0 +1,25 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.dao; + +import com.cloud.utils.db.GenericDaoBase; +import org.apache.cloudstack.gui.theme.GuiThemeVO; +import org.springframework.stereotype.Component; + +@Component +public class GuiThemeDaoImpl extends GenericDaoBase implements GuiThemeDao { +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDao.java new file mode 100644 index 000000000000..af243b1ffa46 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDao.java @@ -0,0 +1,30 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gui.theme.GuiThemeDetailsVO; + +import java.util.List; + +public interface GuiThemeDetailsDao extends GenericDao { + List listGuiThemeIdsByCommonName(String commonName); + + List listGuiThemeIdsByDomainUuids(String domainUuid); + + void expungeByGuiThemeId(long guiThemeId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java new file mode 100644 index 000000000000..b0969833eb01 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeDetailsDaoImpl.java @@ -0,0 +1,126 @@ +// 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.gui.theme.dao; + +import com.cloud.domain.DomainVO; +import com.cloud.domain.dao.DomainDao; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.JoinBuilder; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.gui.theme.GuiThemeDetailsVO; +import org.apache.cloudstack.gui.theme.GuiThemeVO; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +@Component +public class GuiThemeDetailsDaoImpl extends GenericDaoBase implements GuiThemeDetailsDao { + + @Inject + DomainDao domainDao; + + @Inject + GuiThemeDao guiThemeDao; + + public List listGuiThemeIdsByCommonName(String commonName) { + GenericSearchBuilder detailsDaoSearchBuilder = createSearchBuilder(Long.class); + detailsDaoSearchBuilder.selectFields(detailsDaoSearchBuilder.entity().getGuiThemeId()); + detailsDaoSearchBuilder.and("commonNameType", detailsDaoSearchBuilder.entity().getType(), SearchCriteria.Op.EQ); + detailsDaoSearchBuilder.and().op("firstReplace", detailsDaoSearchBuilder.entity().getValue(), SearchCriteria.Op.LIKE_REPLACE); + detailsDaoSearchBuilder.or("secondReplace", detailsDaoSearchBuilder.entity().getValue(), SearchCriteria.Op.LIKE_REPLACE).cp(); + detailsDaoSearchBuilder.done(); + + SearchCriteria searchCriteria = detailsDaoSearchBuilder.create(); + searchCriteria.setParameters("commonNameType", "commonName"); + searchCriteria.setParameters("firstReplace", commonName, "*", "%"); + searchCriteria.setParameters("secondReplace", commonName, "*.", "%"); + + return customSearch(searchCriteria, null); + } + + public List listGuiThemeIdsByDomainUuids(String domainUuid) { + List guiThemeIds = new ArrayList<>(); + String requestedDomainPath = domainDao.findByUuid(domainUuid).getPath(); + + SearchBuilder domainSearchBuilderPathLike = domainDao.createSearchBuilder(); + domainSearchBuilderPathLike.and("pathLike", domainSearchBuilderPathLike.entity().getPath(), SearchCriteria.Op.LIKE_CONCAT); + + SearchBuilder domainSearchBuilderPathEq = domainDao.createSearchBuilder(); + domainSearchBuilderPathEq.and("pathEq", domainSearchBuilderPathEq.entity().getPath(), SearchCriteria.Op.EQ); + + GenericSearchBuilder detailsSearchBuilderPathLike = createDetailsSearchBuilder(domainSearchBuilderPathLike); + SearchCriteria searchCriteriaDomainPathLike = setParametersDomainPathLike(detailsSearchBuilderPathLike, requestedDomainPath); + + GenericSearchBuilder detailsSearchBuilderPathEq = createDetailsSearchBuilder(domainSearchBuilderPathEq); + SearchCriteria searchCriteriaDomainPathEq = setParametersDomainPathEq(detailsSearchBuilderPathEq, requestedDomainPath); + + guiThemeIds.addAll(customSearch(searchCriteriaDomainPathLike, null)); + guiThemeIds.addAll(customSearch(searchCriteriaDomainPathEq, null)); + return guiThemeIds; + } + + private SearchCriteria setParametersDomainPathLike(GenericSearchBuilder detailsSearchBuilderPathLike, String requestedDomainPath) { + SearchCriteria searchCriteria = detailsSearchBuilderPathLike.create(); + searchCriteria.setParameters("domainUuidType", "domain"); + searchCriteria.setJoinParameters("domainJoin", "pathLike", requestedDomainPath, "%"); + searchCriteria.setJoinParameters("guiThemesJoin", "recursiveDomains", true); + + return searchCriteria; + } + + private SearchCriteria setParametersDomainPathEq(GenericSearchBuilder detailsSearchBuilderPathEq, String requestedDomainPath) { + SearchCriteria searchCriteria = detailsSearchBuilderPathEq.create(); + searchCriteria.setParameters("domainUuidType", "domain"); + searchCriteria.setJoinParameters("domainJoin", "pathEq", requestedDomainPath); + searchCriteria.setJoinParameters("guiThemesJoin", "recursiveDomains", false); + + return searchCriteria; + } + + private GenericSearchBuilder createDetailsSearchBuilder(SearchBuilder domainSearchBuilder) { + SearchBuilder guiThemeDaoSearchBuilder = guiThemeDao.createSearchBuilder(); + guiThemeDaoSearchBuilder.and("recursiveDomains", guiThemeDaoSearchBuilder.entity().isRecursiveDomains(), SearchCriteria.Op.EQ); + + GenericSearchBuilder guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder = createSearchBuilder(Long.class); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.selectFields(guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getGuiThemeId()); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.and("domainUuidType", guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getType(), SearchCriteria.Op.EQ); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.join("domainJoin", domainSearchBuilder, domainSearchBuilder.entity().getUuid(), + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getValue(), JoinBuilder.JoinType.INNER); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.join("guiThemesJoin", guiThemeDaoSearchBuilder, guiThemeDaoSearchBuilder.entity().getId(), + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.entity().getGuiThemeId(), JoinBuilder.JoinType.INNER); + + domainSearchBuilder.done(); + guiThemeDaoSearchBuilder.done(); + guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder.done(); + + return guiThemesDetailsJoinDomainJoinGuiThemesSearchBuilder; + } + + public void expungeByGuiThemeId(long guiThemeId) { + SearchBuilder searchBuilder = createSearchBuilder(); + searchBuilder.and("guiThemeId", searchBuilder.entity().getGuiThemeId(), SearchCriteria.Op.EQ); + searchBuilder.done(); + + SearchCriteria searchCriteria = searchBuilder.create(); + searchCriteria.setParameters("guiThemeId", guiThemeId); + expunge(searchCriteria); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDao.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDao.java new file mode 100644 index 000000000000..740199cfca73 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDao.java @@ -0,0 +1,31 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.gui.theme.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.gui.theme.GuiThemeJoinVO; + +import java.util.List; + +public interface GuiThemeJoinDao extends GenericDao { + GuiThemeJoinVO findDefaultTheme(); + + Pair, Integer> listGuiThemesWithNoAuthentication(String commonName); + + Pair, Integer> listGuiThemes(Long id, String name, String commonName, String domainUuid, String accountUuid, boolean listAll, boolean showRemoved, Boolean showPublic); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java new file mode 100644 index 000000000000..ce6f70558128 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/gui/theme/dao/GuiThemeJoinDaoImpl.java @@ -0,0 +1,139 @@ +// 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.gui.theme.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.gui.theme.GuiThemeJoinVO; +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import java.util.ArrayList; +import java.util.List; + +@Component +public class GuiThemeJoinDaoImpl extends GenericDaoBase implements GuiThemeJoinDao { + @Inject + GuiThemeDetailsDao guiThemeDetailsDao; + + public static final Long INVALID_ID = -1L; + + public GuiThemeJoinVO findDefaultTheme() { + SearchBuilder searchBuilder = createSearchBuilder(); + searchBuilder.and("commonNames", searchBuilder.entity().getCommonNames(), SearchCriteria.Op.NULL); + searchBuilder.and("domainUuids", searchBuilder.entity().getDomains(), SearchCriteria.Op.NULL); + searchBuilder.and("accountUuids", searchBuilder.entity().getAccounts(), SearchCriteria.Op.NULL); + searchBuilder.done(); + + SearchCriteria searchCriteria = searchBuilder.create(); + + return findOneBy(searchCriteria); + } + + public Pair, Integer> listGuiThemesWithNoAuthentication(String commonName) { + SearchCriteria searchCriteria = createGuiThemeSearchCriteria(null, null, commonName, null, null, null, false); + return searchOrderByCreatedDate(searchCriteria, false); + } + + public Pair, Integer> listGuiThemes(Long id, String name, String commonName, String domainUuid, String accountUuid, boolean listAll, + boolean showRemoved, Boolean showPublic) { + SearchCriteria searchCriteria = createGuiThemeSearchCriteria(id, name, commonName, domainUuid, accountUuid, showPublic, listAll); + + if (listAll) { + showRemoved = false; + } + + return searchOrderByCreatedDate(searchCriteria, showRemoved); + } + + private Pair, Integer> searchOrderByCreatedDate(SearchCriteria searchCriteria, boolean showRemoved) { + Filter filter = new Filter(GuiThemeJoinVO.class, "created", false); + return searchAndCount(searchCriteria, filter, showRemoved); + } + + private SearchCriteria createGuiThemeSearchCriteria(Long id, String name, String commonName, String domainUuid, String accountUuid, Boolean showPublic, boolean listAll) { + SearchCriteria searchCriteria = createGuiThemeJoinSearchBuilder(listAll, showPublic).create(); + List idList = new ArrayList<>(); + + if (id != null) { + idList.add(id); + } + + searchCriteria.setParametersIfNotNull("name", name); + searchCriteria.setParametersIfNotNull("isPublic", showPublic); + + if (StringUtils.isNotBlank(accountUuid)) { + searchCriteria.setParameters("accountUuid", "%" + accountUuid + "%"); + } + + if (StringUtils.isNotBlank(commonName)) { + setGuiThemeIdsFilteredByType(idList, ApiConstants.COMMON_NAME, commonName); + } + + if (StringUtils.isNotBlank(domainUuid)) { + setGuiThemeIdsFilteredByType(idList, ApiConstants.DOMAIN, domainUuid); + } + + searchCriteria.setParametersIfNotNull("idIn", idList.toArray()); + + return searchCriteria; + } + + /** + * Sets the `id IN ( )` clause of the query. If the informed value of common name or domain ID does not retrieve any GUI theme ID; then, an invalid ID (-1) is passed to the + * list, as not a single entity has this ID. This is necessary as to set the parameter even if it did not find any GUI theme ID; otherwise, the query would not filter the + * common name or domain ID passed. + */ + public void setGuiThemeIdsFilteredByType(List idList, String type, String value) { + List guiThemeIdsFilteredByType = new ArrayList<>(); + + switch (type) { + case ApiConstants.COMMON_NAME: + guiThemeIdsFilteredByType = guiThemeDetailsDao.listGuiThemeIdsByCommonName(value); + break; + case ApiConstants.DOMAIN: + guiThemeIdsFilteredByType = guiThemeDetailsDao.listGuiThemeIdsByDomainUuids(value); + break; + } + + if (CollectionUtils.isNotEmpty(guiThemeIdsFilteredByType)) { + idList.addAll(guiThemeIdsFilteredByType); + return; + } + logger.trace(String.format("No GUI theme with the specified [%s] with UUID [%s] was found, adding an invalid ID for filtering.", type, value)); + idList.add(INVALID_ID); + } + + private SearchBuilder createGuiThemeJoinSearchBuilder(boolean listAll, Boolean showPublic) { + SearchBuilder guiThemeJoinSearchBuilder = createSearchBuilder(); + guiThemeJoinSearchBuilder.and("idIn", guiThemeJoinSearchBuilder.entity().getId(), SearchCriteria.Op.IN); + guiThemeJoinSearchBuilder.and("name", guiThemeJoinSearchBuilder.entity().getName(), SearchCriteria.Op.EQ); + guiThemeJoinSearchBuilder.and("accountUuid", guiThemeJoinSearchBuilder.entity().getAccounts(), SearchCriteria.Op.LIKE); + + if (!listAll && showPublic != null) { + guiThemeJoinSearchBuilder.and("isPublic", guiThemeJoinSearchBuilder.entity().getIsPublic(), SearchCriteria.Op.EQ); + } + + return guiThemeJoinSearchBuilder; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileDetailsVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileDetailsVO.java new file mode 100644 index 000000000000..cd20b8a74fe5 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileDetailsVO.java @@ -0,0 +1,84 @@ +// 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.kms; + +import org.apache.cloudstack.api.ResourceDetail; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "kms_hsm_profile_details") +public class HSMProfileDetailsVO implements ResourceDetail { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "profile_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value") + private String value; + + public HSMProfileDetailsVO() { + } + + public HSMProfileDetailsVO(long profileId, String name, String value) { + this.resourceId = profileId; + this.name = name; + this.value = value; + } + + @Override + public long getId() { + return id; + } + + @Override + public long getResourceId() { + return resourceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public boolean isDisplay() { + return true; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileVO.java new file mode 100644 index 000000000000..86d57400c0d2 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/HSMProfileVO.java @@ -0,0 +1,183 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms; + +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import java.util.Date; +import java.util.UUID; + +@Entity +@Table(name = "kms_hsm_profiles") +public class HSMProfileVO implements HSMProfile { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "uuid") + private String uuid; + + @Column(name = "name") + private String name; + + @Column(name = "protocol") + private String protocol; + + @Column(name = "account_id") + private Long accountId; + + @Column(name = "domain_id") + private Long domainId; + + @Column(name = "zone_id") + private Long zoneId; + + @Column(name = "vendor_name") + private String vendorName; + + @Column(name = "enabled") + private boolean enabled; + + @Column(name = "is_public") + private boolean isPublic; + + @Column(name = "created") + private Date created; + + @Column(name = "removed") + private Date removed; + + public HSMProfileVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + this.isPublic = false; + } + + public HSMProfileVO(String name, String protocol, Long accountId, Long domainId, Long zoneId, String vendorName) { + this.uuid = UUID.randomUUID().toString(); + this.name = name; + this.protocol = protocol; + this.accountId = accountId; + this.domainId = domainId; + this.zoneId = zoneId; + this.vendorName = vendorName; + this.enabled = true; + this.isPublic = false; + this.created = new Date(); + } + + @Override + public String toString() { + return String.format("HSMProfileVO %s", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "name", "protocol", "system", "enabled")); + } + + @Override + public long getId() { + return id; + } + + @Override + public String getUuid() { + return uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getProtocol() { + return protocol; + } + + @Override + public long getAccountId() { + return accountId == null ? -1 : accountId; + } + + @Override + public long getDomainId() { + return domainId == null ? -1 : domainId; + } + + @Override + public Long getZoneId() { + return zoneId; + } + + @Override + public String getVendorName() { + return vendorName; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public Class getEntityType() { + return HSMProfile.class; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setVendorName(String vendorName) { + this.vendorName = vendorName; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean getIsPublic() { + return isPublic; + } + + public void setIsPublic(boolean isPublic) { + this.isPublic = isPublic; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKekVersionVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKekVersionVO.java new file mode 100644 index 000000000000..1dacdff68027 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKekVersionVO.java @@ -0,0 +1,193 @@ +// 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.kms; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; +import java.util.UUID; + +/** + * Database entity for KEK versions. + * Tracks multiple KEK versions per KMS key to support gradual rotation. + * During rotation, a new version is created (status=Active) and old versions + * are marked as Previous (still usable for decryption) or Archived (no longer used). + */ +@Entity +@Table(name = "kms_kek_versions") +public class KMSKekVersionVO { + + public enum Status { + /** + * Used for new encryption operations + */ + Active, + /** + * Still usable for decryption during key rotation + */ + Previous, + /** + * No longer used; all wrapped keys have been re-encrypted + */ + Archived + } + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid; + + @Column(name = "kms_key_id", nullable = false) + private Long kmsKeyId; + + @Column(name = "version_number", nullable = false) + private Integer versionNumber; + + @Column(name = "kek_label", nullable = false) + private String kekLabel; + + @Column(name = "status", nullable = false, length = 32) + @Enumerated(EnumType.STRING) + private Status status; + + @Column(name = "hsm_profile_id") + private Long hsmProfileId; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(TemporalType.TIMESTAMP) + private Date removed; + + public KMSKekVersionVO(Long kmsKeyId, Integer versionNumber, String kekLabel) { + this(); + this.kmsKeyId = kmsKeyId; + this.versionNumber = versionNumber; + this.kekLabel = kekLabel; + this.status = Status.Active; + } + + public KMSKekVersionVO(Long kmsKeyId, String kekLabel) { + this(); + this.kmsKeyId = kmsKeyId; + this.kekLabel = kekLabel; + this.status = Status.Active; + } + + public KMSKekVersionVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + this.status = Status.Active; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public Long getKmsKeyId() { + return kmsKeyId; + } + + public void setKmsKeyId(Long kmsKeyId) { + this.kmsKeyId = kmsKeyId; + } + + public Integer getVersionNumber() { + return versionNumber; + } + + public void setVersionNumber(Integer versionNumber) { + this.versionNumber = versionNumber; + } + + public String getKekLabel() { + return kekLabel; + } + + public void setKekLabel(String kekLabel) { + this.kekLabel = kekLabel; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public Long getHsmProfileId() { + return hsmProfileId; + } + + public void setHsmProfileId(Long hsmProfileId) { + this.hsmProfileId = hsmProfileId; + } + + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public String toString() { + return String.format("KMSKekVersion %s", + ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "kmsKeyId", "versionNumber", "status", "kekLabel")); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKeyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKeyVO.java new file mode 100644 index 000000000000..36c68de3744b --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSKeyVO.java @@ -0,0 +1,264 @@ +// 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.kms; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.framework.kms.KeyPurpose; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Date; +import java.util.UUID; + +/** + * Database entity for KMS Key (Key Encryption Key) metadata. + * Tracks ownership, purpose, and lifecycle of KEKs used in envelope encryption. + */ +@Entity +@Table(name = "kms_keys") +public class KMSKeyVO implements KMSKey { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid; + + @Column(name = "name", nullable = false) + private String name; + + @Column(name = "description", length = 1024) + private String description; + + @Column(name = "kek_label", nullable = false) + private String kekLabel; + + @Column(name = "purpose", nullable = false, length = 32) + @Enumerated(EnumType.STRING) + private KeyPurpose purpose; + + @Column(name = "account_id", nullable = false) + private Long accountId; + + @Column(name = "domain_id", nullable = false) + private Long domainId; + + @Column(name = "zone_id", nullable = false) + private Long zoneId; + + @Column(name = "algorithm", nullable = false, length = 64) + private String algorithm; + + @Column(name = "key_bits", nullable = false) + private Integer keyBits; + + @Column(name = "enabled", nullable = false) + private boolean enabled; + + @Column(name = "hsm_profile_id") + private Long hsmProfileId; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(TemporalType.TIMESTAMP) + private Date removed; + + public KMSKeyVO(String name, String description, String kekLabel, + KeyPurpose purpose, Long accountId, Long domainId, + Long zoneId, String algorithm, Integer keyBits + ) { + this(); + this.name = name; + this.description = description; + this.kekLabel = kekLabel; + this.purpose = purpose; + this.accountId = accountId; + this.domainId = domainId; + this.zoneId = zoneId; + this.algorithm = algorithm; + this.keyBits = keyBits; + } + + public KMSKeyVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + this.enabled = true; + } + + @Override + public long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + @Override + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getKekLabel() { + return kekLabel; + } + + @Override + public KeyPurpose getPurpose() { + return purpose; + } + + @Override + public Long getZoneId() { + return zoneId; + } + + @Override + public String getAlgorithm() { + return algorithm; + } + + @Override + public Integer getKeyBits() { + return keyBits; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public Date getCreated() { + return created; + } + + @Override + public Date getRemoved() { + return removed; + } + + @Override + public Long getHsmProfileId() { + return hsmProfileId; + } + + public void setHsmProfileId(Long hsmProfileId) { + this.hsmProfileId = hsmProfileId; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + public void setCreated(Date created) { + this.created = created; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public void setKeyBits(Integer keyBits) { + this.keyBits = keyBits; + } + + public void setAlgorithm(String algorithm) { + this.algorithm = algorithm; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public void setPurpose(KeyPurpose purpose) { + this.purpose = purpose; + } + + public void setKekLabel(String kekLabel) { + this.kekLabel = kekLabel; + } + + public void setDescription(String description) { + this.description = description; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public long getAccountId() { + return accountId; + } + + public void setAccountId(Long accountId) { + this.accountId = accountId; + } + + @Override + public long getDomainId() { + return domainId; + } + + public void setDomainId(Long domainId) { + this.domainId = domainId; + } + + @Override + public Class getEntityType() { + return KMSKey.class; + } + + @Override + public String toString() { + return String.format("KMSKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "name", "purpose", + "accountId", "zoneId", "enabled" + )); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java new file mode 100644 index 000000000000..8a763623fba7 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/KMSWrappedKeyVO.java @@ -0,0 +1,176 @@ +// 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.kms; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; +import java.util.Arrays; +import java.util.Date; +import java.util.UUID; + +/** + * Database entity for storing wrapped (encrypted) Data Encryption Keys. + * Each entry represents a DEK that has been encrypted by a Key Encryption Key (KEK). + * KEK metadata is stored in kms_keys table via the kms_key_id foreign key. + */ +@Entity +@Table(name = "kms_wrapped_key") +public class KMSWrappedKeyVO { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + + @Column(name = "uuid", nullable = false) + private String uuid; + + @Column(name = "kms_key_id") + private Long kmsKeyId; + + @Column(name = "kek_version_id") + private Long kekVersionId; + + @Column(name = "zone_id", nullable = false) + private Long zoneId; + + @Column(name = "wrapped_blob", nullable = false) + private byte[] wrappedBlob; + + @Column(name = GenericDao.CREATED_COLUMN, nullable = false) + @Temporal(TemporalType.TIMESTAMP) + private Date created; + + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(TemporalType.TIMESTAMP) + private Date removed; + + public KMSWrappedKeyVO(KMSKeyVO kmsKey, byte[] wrappedBlob) { + this(); + this.kmsKeyId = kmsKey.getId(); + this.zoneId = kmsKey.getZoneId(); + this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null; + } + + public KMSWrappedKeyVO() { + this.uuid = UUID.randomUUID().toString(); + this.created = new Date(); + } + + public KMSWrappedKeyVO(KMSKeyVO kmsKey, Long kekVersionId, byte[] wrappedBlob) { + this(); + this.kmsKeyId = kmsKey.getId(); + this.kekVersionId = kekVersionId; + this.zoneId = kmsKey.getZoneId(); + this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null; + } + + public KMSWrappedKeyVO(Long kmsKeyId, Long zoneId, byte[] wrappedBlob) { + this(); + this.kmsKeyId = kmsKeyId; + this.zoneId = zoneId; + this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null; + } + + public KMSWrappedKeyVO(Long kmsKeyId, Long kekVersionId, Long zoneId, byte[] wrappedBlob) { + this(); + this.kmsKeyId = kmsKeyId; + this.kekVersionId = kekVersionId; + this.zoneId = zoneId; + this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null; + } + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getUuid() { + return uuid; + } + + public void setUuid(String uuid) { + this.uuid = uuid; + } + + public Long getKmsKeyId() { + return kmsKeyId; + } + + public void setKmsKeyId(Long kmsKeyId) { + this.kmsKeyId = kmsKeyId; + } + + public Long getKekVersionId() { + return kekVersionId; + } + + public void setKekVersionId(Long kekVersionId) { + this.kekVersionId = kekVersionId; + } + + public Long getZoneId() { + return zoneId; + } + + public void setZoneId(Long zoneId) { + this.zoneId = zoneId; + } + + public byte[] getWrappedBlob() { + return wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null; + } + + public void setWrappedBlob(byte[] wrappedBlob) { + this.wrappedBlob = wrappedBlob != null ? Arrays.copyOf(wrappedBlob, wrappedBlob.length) : null; + } + + public Date getCreated() { + return created; + } + + public void setCreated(Date created) { + this.created = created; + } + + public Date getRemoved() { + return removed; + } + + public void setRemoved(Date removed) { + this.removed = removed; + } + + @Override + public String toString() { + return String.format("KMSWrappedKey %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "kmsKeyId", "kekVersionId", "zoneId", "created", "removed")); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDao.java new file mode 100644 index 000000000000..a2202a18bfc1 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDao.java @@ -0,0 +1,24 @@ +// 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.kms.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.kms.HSMProfileVO; + +public interface HSMProfileDao extends GenericDao { +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDaoImpl.java new file mode 100644 index 000000000000..b063d2cfe749 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDaoImpl.java @@ -0,0 +1,29 @@ +// 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.kms.dao; + +import com.cloud.utils.db.GenericDaoBase; +import org.apache.cloudstack.kms.HSMProfileVO; +import org.springframework.stereotype.Component; + +@Component +public class HSMProfileDaoImpl extends GenericDaoBase implements HSMProfileDao { + public HSMProfileDaoImpl() { + super(); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDao.java new file mode 100644 index 000000000000..0d5c71b9e5f0 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDao.java @@ -0,0 +1,33 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.kms.HSMProfileDetailsVO; + +import java.util.List; + +public interface HSMProfileDetailsDao extends GenericDao { + List listByProfileId(long profileId); + + void persist(long profileId, String name, String value); + + HSMProfileDetailsVO findDetail(long profileId, String name); + + void deleteDetails(long profileId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDaoImpl.java new file mode 100644 index 000000000000..59c0ec43259c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/HSMProfileDetailsDaoImpl.java @@ -0,0 +1,75 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.SearchCriteria.Op; +import org.apache.cloudstack.kms.HSMProfileDetailsVO; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class HSMProfileDetailsDaoImpl extends GenericDaoBase implements HSMProfileDetailsDao { + + protected SearchBuilder ProfileSearch; + protected SearchBuilder DetailSearch; + + public HSMProfileDetailsDaoImpl() { + super(); + + ProfileSearch = createSearchBuilder(); + ProfileSearch.and("profileId", ProfileSearch.entity().getResourceId(), Op.EQ); + ProfileSearch.done(); + + DetailSearch = createSearchBuilder(); + DetailSearch.and("profileId", DetailSearch.entity().getResourceId(), Op.EQ); + DetailSearch.and("name", DetailSearch.entity().getName(), Op.EQ); + DetailSearch.done(); + } + + @Override + public List listByProfileId(long profileId) { + SearchCriteria sc = ProfileSearch.create(); + sc.setParameters("profileId", profileId); + return listBy(sc); + } + + @Override + public void persist(long profileId, String name, String value) { + HSMProfileDetailsVO vo = new HSMProfileDetailsVO(profileId, name, value); + persist(vo); + } + + @Override + public HSMProfileDetailsVO findDetail(long profileId, String name) { + SearchCriteria sc = DetailSearch.create(); + sc.setParameters("profileId", profileId); + sc.setParameters("name", name); + return findOneBy(sc); + } + + @Override + public void deleteDetails(long profileId) { + SearchCriteria sc = ProfileSearch.create(); + sc.setParameters("profileId", profileId); + remove(sc); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java new file mode 100644 index 000000000000..dbe4ca5f648a --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDao.java @@ -0,0 +1,43 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.kms.KMSKekVersionVO; + +import java.util.List; + +public interface KMSKekVersionDao extends GenericDao { + + KMSKekVersionVO getActiveVersion(Long kmsKeyId); + + /** + * Returns Active and Previous versions (usable for decryption) + */ + List getVersionsForDecryption(Long kmsKeyId); + + List listByKmsKeyId(Long kmsKeyId); + + KMSKekVersionVO findByKmsKeyIdAndVersion(Long kmsKeyId, Integer versionNumber); + + KMSKekVersionVO findByKekLabel(String kekLabel); + + List findByStatus(KMSKekVersionVO.Status status); + + List listByHsmProfileId(Long hsmProfileId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java new file mode 100644 index 000000000000..841ab8c03d9b --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKekVersionDaoImpl.java @@ -0,0 +1,98 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.kms.KMSKekVersionVO; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; + +@Component +public class KMSKekVersionDaoImpl extends GenericDaoBase implements KMSKekVersionDao { + + private final SearchBuilder allFieldSearch; + + public KMSKekVersionDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), SearchCriteria.Op.EQ); + allFieldSearch.and("status", allFieldSearch.entity().getStatus(), SearchCriteria.Op.IN); + allFieldSearch.and("versionNumber", allFieldSearch.entity().getVersionNumber(), SearchCriteria.Op.EQ); + allFieldSearch.and("kekLabel", allFieldSearch.entity().getKekLabel(), SearchCriteria.Op.EQ); + allFieldSearch.and("hsmProfileId", allFieldSearch.entity().getHsmProfileId(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + } + + @Override + public KMSKekVersionVO getActiveVersion(Long kmsKeyId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kmsKeyId", kmsKeyId); + sc.setParameters("status", KMSKekVersionVO.Status.Active); + return findOneBy(sc); + } + + @Override + public List getVersionsForDecryption(Long kmsKeyId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kmsKeyId", kmsKeyId); + sc.setParameters("status", KMSKekVersionVO.Status.Active, KMSKekVersionVO.Status.Previous); + return listBy(sc); + } + + @Override + public List listByKmsKeyId(Long kmsKeyId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kmsKeyId", kmsKeyId); + return listBy(sc); + } + + @Override + public KMSKekVersionVO findByKmsKeyIdAndVersion(Long kmsKeyId, Integer versionNumber) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kmsKeyId", kmsKeyId); + sc.setParameters("versionNumber", versionNumber); + return findOneBy(sc); + } + + @Override + public KMSKekVersionVO findByKekLabel(String kekLabel) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kekLabel", kekLabel); + return findOneBy(sc); + } + + @Override + public List findByStatus(KMSKekVersionVO.Status status) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("status", status); + return listBy(sc); + } + + @Override + public List listByHsmProfileId(Long hsmProfileId) { + if (hsmProfileId == null) { + return new ArrayList<>(); + } + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("hsmProfileId", hsmProfileId); + return listBy(sc); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDao.java new file mode 100644 index 000000000000..4a03f2ff96c2 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDao.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.framework.kms.KeyPurpose; +import org.apache.cloudstack.kms.KMSKeyVO; + +import java.util.List; + +public interface KMSKeyDao extends GenericDao { + + List listByAccount(Long accountId, KeyPurpose purpose, Boolean enabled); + + List listByZone(Long zoneId, KeyPurpose purpose, Boolean enabled); + + long countByHsmProfileId(Long hsmProfileId); + + KMSKeyVO findByNameAndAccountId(String name, long accountId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDaoImpl.java new file mode 100644 index 000000000000..ac442c19d1ab --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSKeyDaoImpl.java @@ -0,0 +1,83 @@ +// 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.kms.dao; + +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.framework.kms.KeyPurpose; +import org.apache.cloudstack.kms.KMSKeyVO; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class KMSKeyDaoImpl extends GenericDaoBase implements KMSKeyDao { + + private final SearchBuilder allFieldSearch; + + public KMSKeyDaoImpl() { + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("name", allFieldSearch.entity().getName(), SearchCriteria.Op.EQ); + allFieldSearch.and("kekLabel", allFieldSearch.entity().getKekLabel(), SearchCriteria.Op.EQ); + allFieldSearch.and("domainId", allFieldSearch.entity().getDomainId(), SearchCriteria.Op.EQ); + allFieldSearch.and("accountId", allFieldSearch.entity().getAccountId(), SearchCriteria.Op.EQ); + allFieldSearch.and("purpose", allFieldSearch.entity().getPurpose(), SearchCriteria.Op.EQ); + allFieldSearch.and("enabled", allFieldSearch.entity().isEnabled(), SearchCriteria.Op.EQ); + allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + allFieldSearch.and("hsmProfileId", allFieldSearch.entity().getHsmProfileId(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + } + + @Override + public List listByAccount(Long accountId, KeyPurpose purpose, Boolean enabled) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("accountId", accountId); + sc.setParametersIfNotNull("purpose", purpose); + sc.setParametersIfNotNull("enabled", enabled); + return listBy(sc); + } + + @Override + public List listByZone(Long zoneId, KeyPurpose purpose, Boolean enabled) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("zoneId", zoneId); + sc.setParametersIfNotNull("purpose", purpose); + sc.setParametersIfNotNull("enabled", enabled); + return listBy(sc); + } + + @Override + public long countByHsmProfileId(Long hsmProfileId) { + if (hsmProfileId == null) { + return 0; + } + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("hsmProfileId", hsmProfileId); + Integer count = getCount(sc); + return count != null ? count : 0; + } + + @Override + public KMSKeyVO findByNameAndAccountId(String name, long accountId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("name", name); + sc.setParameters("accountId", accountId); + return findOneBy(sc); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java new file mode 100644 index 000000000000..5e9e418a1667 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDao.java @@ -0,0 +1,35 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.kms.KMSWrappedKeyVO; + +import java.util.List; + +public interface KMSWrappedKeyDao extends GenericDao { + + long countByKmsKeyId(Long kmsKeyId); + + /** + * Limited variant for batch processing during key rotation + */ + List listByKekVersionId(Long kekVersionId, int limit); + + long countByKekVersionId(Long kekVersionId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java new file mode 100644 index 000000000000..fc1a7190ae8e --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/kms/dao/KMSWrappedKeyDaoImpl.java @@ -0,0 +1,70 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package org.apache.cloudstack.kms.dao; + +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.kms.KMSWrappedKeyVO; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class KMSWrappedKeyDaoImpl extends GenericDaoBase implements KMSWrappedKeyDao { + + private final SearchBuilder allFieldSearch; + + public KMSWrappedKeyDaoImpl() { + super(); + + allFieldSearch = createSearchBuilder(); + allFieldSearch.and("kmsKeyId", allFieldSearch.entity().getKmsKeyId(), SearchCriteria.Op.EQ); + allFieldSearch.and("kekVersionId", allFieldSearch.entity().getKekVersionId(), SearchCriteria.Op.EQ); + allFieldSearch.and("zoneId", allFieldSearch.entity().getZoneId(), SearchCriteria.Op.EQ); + allFieldSearch.done(); + } + + @Override + public long countByKmsKeyId(Long kmsKeyId) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kmsKeyId", kmsKeyId); + Integer count = getCount(sc); + return count != null ? count.longValue() : 0L; + } + + @Override + public List listByKekVersionId(Long kekVersionId, int limit) { + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kekVersionId", kekVersionId); + Filter filter = new Filter(limit); + return listBy(sc, filter); + } + + @Override + public long countByKekVersionId(Long kekVersionId) { + if (kekVersionId == null) { + return 0; + } + SearchCriteria sc = allFieldSearch.create(); + sc.setParameters("kekVersionId", kekVersionId); + Integer count = getCount(sc); + return count != null ? count.longValue() : 0L; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java index 4ce7033156fa..47e2d130d871 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/region/gslb/GlobalLoadBalancerRuleVO.java @@ -17,6 +17,7 @@ package org.apache.cloudstack.region.gslb; +import java.util.Date; import java.util.UUID; import javax.persistence.Column; @@ -27,8 +28,11 @@ import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; +import javax.persistence.Temporal; +import javax.persistence.TemporalType; import com.cloud.region.ha.GlobalLoadBalancerRule; +import com.cloud.utils.db.GenericDao; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; @Entity @@ -74,6 +78,10 @@ public class GlobalLoadBalancerRuleVO implements GlobalLoadBalancerRule { @Column(name = "state") GlobalLoadBalancerRule.State state; + @Column(name = GenericDao.REMOVED_COLUMN) + @Temporal(value = TemporalType.TIMESTAMP) + private Date removed; + public GlobalLoadBalancerRuleVO() { uuid = UUID.randomUUID().toString(); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java index e3241731f719..4383278d7c48 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDao.java @@ -21,6 +21,7 @@ import org.apache.cloudstack.api.ResourceDetail; +import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; public interface ResourceDetailsDao extends GenericDao { @@ -32,6 +33,13 @@ public interface ResourceDetailsDao extends GenericDao */ R findDetail(long resourceId, String name); + /** + * Find details by key + * @param key + * @return + */ + List findDetails(String key); + /** * Find details by resourceId and key * @param resourceId @@ -94,6 +102,8 @@ public interface ResourceDetailsDao extends GenericDao Map listDetailsVisibility(long resourceId); + Pair, Map> listDetailsKeyPairsWithVisibility(long resourceId); + void saveDetails(List details); void addDetail(long resourceId, String key, String value, boolean display); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java index 74c4f7bcfe00..8f376a71f66f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/ResourceDetailsDaoBase.java @@ -25,6 +25,7 @@ import com.cloud.utils.StringUtils; import org.apache.commons.collections.CollectionUtils; +import com.cloud.utils.Pair; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.GenericSearchBuilder; @@ -67,6 +68,12 @@ public R findDetail(long resourceId, String name) { return findOneBy(sc); } + public List findDetails(String key) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("name", key); + return listBy(sc); + } + public List findDetails(long resourceId, String key) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("resourceId", resourceId); @@ -130,6 +137,19 @@ public Map listDetailsVisibility(long resourceId) { return details; } + @Override + public Pair, Map> listDetailsKeyPairsWithVisibility(long resourceId) { + SearchCriteria sc = AllFieldsSearch.create(); + sc.setParameters("resourceId", resourceId); + List results = search(sc, null); + Map> partitioned = results.stream() + .collect(Collectors.partitioningBy( + R::isDisplay, + Collectors.toMap(R::getName, R::getValue) + )); + return new Pair<>(partitioned.get(true), partitioned.get(false)); + } + public List listDetails(long resourceId) { SearchCriteria sc = AllFieldsSearch.create(); sc.setParameters("resourceId", resourceId); diff --git a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java index d0cfcc3d4396..93b49bc20a10 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/resourcedetail/UserDetailVO.java @@ -48,6 +48,8 @@ public class UserDetailVO implements ResourceDetail { public static final String Setup2FADetail = "2FASetupStatus"; public static final String PasswordResetToken = "PasswordResetToken"; public static final String PasswordResetTokenExpiryDate = "PasswordResetTokenExpiryDate"; + public static final String PasswordChangeRequired = "PasswordChangeRequired"; + public static final String OauthLogin = "OauthLogin"; public UserDetailVO() { } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java new file mode 100644 index 000000000000..16985e6bac30 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleDetailVO.java @@ -0,0 +1,82 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.schedule; + +import org.apache.cloudstack.api.ResourceDetail; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.Table; + +@Entity +@Table(name = "resource_schedule_details") +public class ResourceScheduleDetailVO implements ResourceDetail { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private long id; + + @Column(name = "schedule_id") + private long resourceId; + + @Column(name = "name") + private String name; + + @Column(name = "value", length = 1024) + private String value; + + @Column(name = "display") + private boolean display = true; + + public ResourceScheduleDetailVO() { + } + + public ResourceScheduleDetailVO(long scheduleId, String name, String value, boolean display) { + this.resourceId = scheduleId; + this.name = name; + this.value = value; + this.display = display; + } + + @Override + public long getId() { + return id; + } + + @Override + public long getResourceId() { + return resourceId; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getValue() { + return value; + } + + @Override + public boolean isDisplay() { + return display; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java similarity index 71% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java index e0065db1e77a..33a09d79239d 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduleVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduleVO.java @@ -16,9 +16,10 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule; +package org.apache.cloudstack.schedule; import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import javax.persistence.Column; @@ -37,8 +38,8 @@ import java.util.UUID; @Entity -@Table(name = "vm_schedule") -public class VMScheduleVO implements VMSchedule { +@Table(name = "resource_schedule") +public class ResourceScheduleVO implements ResourceSchedule { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id", nullable = false) @@ -50,8 +51,12 @@ public class VMScheduleVO implements VMSchedule { @Column(name = "description") String description; - @Column(name = "vm_id", nullable = false) - long vmId; + @Enumerated(value = EnumType.STRING) + @Column(name = "resource_type", nullable = false) + ApiCommandResourceType resourceType; + + @Column(name = "resource_id", nullable = false) + long resourceId; @Column(name = "schedule", nullable = false) String schedule; @@ -60,8 +65,7 @@ public class VMScheduleVO implements VMSchedule { String timeZone; @Column(name = "action", nullable = false) - @Enumerated(value = EnumType.STRING) - Action action; + String actionName; @Column(name = "enabled", nullable = false) boolean enabled; @@ -80,17 +84,19 @@ public class VMScheduleVO implements VMSchedule { @Column(name = GenericDao.REMOVED_COLUMN) Date removed; - public VMScheduleVO() { + public ResourceScheduleVO() { uuid = UUID.randomUUID().toString(); } - public VMScheduleVO(long vmId, String description, String schedule, String timeZone, Action action, Date startDate, Date endDate, boolean enabled) { + public ResourceScheduleVO(ApiCommandResourceType resourceType, long resourceId, String description, String schedule, + String timeZone, String action, Date startDate, Date endDate, boolean enabled) { uuid = UUID.randomUUID().toString(); - this.vmId = vmId; + this.resourceType = resourceType; + this.resourceId = resourceId; this.description = description; this.schedule = schedule; this.timeZone = timeZone; - this.action = action; + this.actionName = action; this.startDate = startDate; this.endDate = endDate; this.enabled = enabled; @@ -98,7 +104,8 @@ public VMScheduleVO(long vmId, String description, String schedule, String timeZ @Override public String toString() { - return String.format("VMSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields(this, "id", "uuid", "action", "description")); + return String.format("ResourceSchedule %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( + this, "id", "uuid", "resourceType", "actionName", "description", "enabled")); } @Override @@ -111,14 +118,25 @@ public long getId() { return id; } - public long getVmId() { - return vmId; + @Override + public ApiCommandResourceType getResourceType() { + return resourceType; + } + + public void setResourceType(ApiCommandResourceType resourceType) { + this.resourceType = resourceType; + } + + @Override + public long getResourceId() { + return resourceId; } - public void setVmId(long vmId) { - this.vmId = vmId; + public void setResourceId(long resourceId) { + this.resourceId = resourceId; } + @Override public String getDescription() { return description; } @@ -127,6 +145,7 @@ public void setDescription(String description) { this.description = description; } + @Override public String getSchedule() { return schedule.substring(2); } @@ -144,14 +163,16 @@ public void setTimeZone(String timeZone) { this.timeZone = timeZone; } - public Action getAction() { - return action; + @Override + public String getActionName() { + return actionName; } - public void setAction(Action action) { - this.action = action; + public void setActionName(String action) { + this.actionName = action; } + @Override public boolean getEnabled() { return enabled; } @@ -183,6 +204,7 @@ public ZoneId getTimeZoneId() { return TimeZone.getTimeZone(getTimeZone()).toZoneId(); } + @Override public Date getCreated() { return created; } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java similarity index 64% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java index 775e9cfe40cf..ca03056b67a8 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/VMScheduledJobVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/ResourceScheduledJobVO.java @@ -16,8 +16,9 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule; +package org.apache.cloudstack.schedule; +import org.apache.cloudstack.api.ApiCommandResourceType; import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; import javax.persistence.Column; @@ -34,8 +35,8 @@ import java.util.UUID; @Entity -@Table(name = "vm_scheduled_job") -public class VMScheduledJobVO implements VMScheduledJob { +@Table(name = "resource_scheduled_job") +public class ResourceScheduledJobVO implements ResourceScheduledJob { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "id") @@ -44,41 +45,44 @@ public class VMScheduledJobVO implements VMScheduledJob { @Column(name = "uuid", nullable = false) String uuid; - @Column(name = "vm_id", nullable = false) - long vmId; + @Enumerated(value = EnumType.STRING) + @Column(name = "resource_type", nullable = false) + ApiCommandResourceType resourceType; + + @Column(name = "resource_id", nullable = false) + long resourceId; - @Column(name = "vm_schedule_id", nullable = false) - long vmScheduleId; + @Column(name = "schedule_id", nullable = false) + long scheduleId; @Column(name = "async_job_id") Long asyncJobId; @Column(name = "action", nullable = false) - @Enumerated(value = EnumType.STRING) - VMSchedule.Action action; + String actionName; @Column(name = "scheduled_timestamp") @Temporal(value = TemporalType.TIMESTAMP) Date scheduledTime; - public VMScheduledJobVO() { + public ResourceScheduledJobVO() { uuid = UUID.randomUUID().toString(); } - public VMScheduledJobVO(long vmId, long vmScheduleId, VMSchedule.Action action, Date scheduledTime) { + public ResourceScheduledJobVO(ApiCommandResourceType resourceType, long resourceId, long scheduleId, String action, Date scheduledTime) { uuid = UUID.randomUUID().toString(); - this.vmId = vmId; - this.vmScheduleId = vmScheduleId; - this.action = action; + this.resourceType = resourceType; + this.resourceId = resourceId; + this.scheduleId = scheduleId; + this.actionName = action; this.scheduledTime = scheduledTime; } - @Override public String toString() { - return String.format("VMScheduledJob %s", + return String.format("ResourceScheduledJob %s", ReflectionToStringBuilderUtils.reflectOnlySelectedFields( - this, "id", "uuid", "action", "vmScheduleId", "vmId", "asyncJobId")); + this, "id", "uuid", "resourceType", "actionName", "scheduleId", "resourceId", "asyncJobId")); } @Override @@ -92,13 +96,18 @@ public long getId() { } @Override - public long getVmId() { - return vmId; + public ApiCommandResourceType getResourceType() { + return resourceType; } @Override - public long getVmScheduleId() { - return vmScheduleId; + public long getResourceId() { + return resourceId; + } + + @Override + public long getScheduleId() { + return scheduleId; } @Override @@ -112,8 +121,12 @@ public void setAsyncJobId(long asyncJobId) { } @Override - public VMSchedule.Action getAction() { - return action; + public String getActionName() { + return actionName; + } + + public void setActionName(String action) { + this.actionName = action; } @Override diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java new file mode 100644 index 000000000000..ee56991fb9ac --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDao.java @@ -0,0 +1,40 @@ +/* + * 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.schedule.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.GenericDao; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.schedule.ResourceScheduleVO; + +import java.util.List; + +public interface ResourceScheduleDao extends GenericDao { + List listAllActiveSchedules(ApiCommandResourceType resourceType); + + long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List ids); + + long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId); + + Pair, Integer> searchAndCount(List ids, ApiCommandResourceType resourceType, Long resourceId, + String action, Boolean enabled, Long offset, Long limit); + + SearchCriteria getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId); +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDaoImpl.java new file mode 100644 index 000000000000..7217d1359a81 --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDaoImpl.java @@ -0,0 +1,109 @@ +/* + * 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.schedule.dao; + +import com.cloud.utils.Pair; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericDaoBase; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.api.ApiConstants; +import org.apache.cloudstack.schedule.ResourceScheduleVO; +import org.apache.commons.collections4.CollectionUtils; +import org.springframework.stereotype.Component; + +import java.util.List; + +@Component +public class ResourceScheduleDaoImpl extends GenericDaoBase implements ResourceScheduleDao { + + private final SearchBuilder activeScheduleSearch; + private final SearchBuilder allSearch; + + static final String RESOURCE_TYPE = "resourceType"; + static final String RESOURCE_ID = "resourceId"; + + public ResourceScheduleDaoImpl() { + super(); + + activeScheduleSearch = createSearchBuilder(); + activeScheduleSearch.and(RESOURCE_TYPE, activeScheduleSearch.entity().getResourceType(), SearchCriteria.Op.EQ); + activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ); + activeScheduleSearch.done(); + + allSearch = createSearchBuilder(); + allSearch.and(ApiConstants.ID, allSearch.entity().getId(), SearchCriteria.Op.IN); + allSearch.and(RESOURCE_TYPE, allSearch.entity().getResourceType(), SearchCriteria.Op.EQ); + allSearch.and(RESOURCE_ID, allSearch.entity().getResourceId(), SearchCriteria.Op.EQ); + allSearch.and(ApiConstants.ACTION, allSearch.entity().getActionName(), SearchCriteria.Op.EQ); + allSearch.and(ApiConstants.ENABLED, allSearch.entity().getEnabled(), SearchCriteria.Op.EQ); + allSearch.done(); + } + + @Override + public List listAllActiveSchedules(ApiCommandResourceType resourceType) { + SearchCriteria sc = activeScheduleSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); + sc.setParameters(ApiConstants.ENABLED, true); + return search(sc, null); + } + + @Override + public long removeSchedulesForResourceAndIds(ApiCommandResourceType resourceType, long resourceId, List ids) { + SearchCriteria sc = allSearch.create(); + if (CollectionUtils.isNotEmpty(ids)) { + sc.setParameters(ApiConstants.ID, ids.toArray()); + } + sc.setParameters(RESOURCE_TYPE, resourceType); + sc.setParameters(RESOURCE_ID, resourceId); + return remove(sc); + } + + @Override + public long removeAllSchedulesForResource(ApiCommandResourceType resourceType, long resourceId) { + SearchCriteria sc = allSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); + sc.setParameters(RESOURCE_ID, resourceId); + return remove(sc); + } + + @Override + public Pair, Integer> searchAndCount(List ids, ApiCommandResourceType resourceType, Long resourceId, + String action, Boolean enabled, Long offset, Long limit) { + SearchCriteria sc = allSearch.create(); + if (CollectionUtils.isNotEmpty(ids)) { + sc.setParameters(ApiConstants.ID, ids.toArray()); + } + sc.setParametersIfNotNull(ApiConstants.ENABLED, enabled); + sc.setParametersIfNotNull(ApiConstants.ACTION, action); + sc.setParametersIfNotNull(RESOURCE_TYPE, resourceType); + sc.setParametersIfNotNull(RESOURCE_ID, resourceId); + Filter filter = new Filter(ResourceScheduleVO.class, ApiConstants.ID, false, offset, limit); + return searchAndCount(sc, filter); + } + + @Override + public SearchCriteria getSearchCriteriaForResource(ApiCommandResourceType resourceType, long resourceId) { + SearchCriteria sc = allSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); + sc.setParameters(RESOURCE_ID, resourceId); + return sc; + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDao.java new file mode 100644 index 000000000000..f36ce8d0879c --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDao.java @@ -0,0 +1,24 @@ +// 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.schedule.dao; + +import com.cloud.utils.db.GenericDao; +import org.apache.cloudstack.resourcedetail.ResourceDetailsDao; +import org.apache.cloudstack.schedule.ResourceScheduleDetailVO; + +public interface ResourceScheduleDetailsDao extends GenericDao, ResourceDetailsDao { +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDaoImpl.java new file mode 100644 index 000000000000..3757e41d0cfe --- /dev/null +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduleDetailsDaoImpl.java @@ -0,0 +1,28 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.schedule.dao; + +import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; +import org.apache.cloudstack.schedule.ResourceScheduleDetailVO; + +public class ResourceScheduleDetailsDaoImpl extends ResourceDetailsDaoBase implements ResourceScheduleDetailsDao { + + @Override + public void addDetail(long resourceId, String key, String value, boolean display) { + super.addDetail(new ResourceScheduleDetailVO(resourceId, key, value, display)); + } +} diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDao.java similarity index 57% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDao.java index b8c808b5bfc9..16210802235f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDao.java @@ -16,22 +16,21 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule.dao; +package org.apache.cloudstack.schedule.dao; -import com.cloud.utils.Pair; import com.cloud.utils.db.GenericDao; -import com.cloud.utils.db.SearchCriteria; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleVO; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.schedule.ResourceScheduledJobVO; +import java.util.Date; import java.util.List; -public interface VMScheduleDao extends GenericDao { - List listAllActiveSchedules(); +public interface ResourceScheduledJobDao extends GenericDao { + List listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp); - long removeSchedulesForVmIdAndIds(Long vmId, List ids); + int expungeJobsForSchedules(List scheduleIds, Date dateAfter); - Pair, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit); + int expungeJobsBefore(ApiCommandResourceType resourceType, Date currentTimestamp); - SearchCriteria getSearchCriteriaForVMId(Long vmId); + ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDaoImpl.java similarity index 55% rename from engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.java rename to engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDaoImpl.java index 2f08a41b92e4..02c1acb6c719 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduledJobDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/schedule/dao/ResourceScheduledJobDaoImpl.java @@ -16,13 +16,14 @@ * specific language governing permissions and limitations * under the License. */ -package org.apache.cloudstack.vm.schedule.dao; +package org.apache.cloudstack.schedule.dao; import com.cloud.utils.db.Filter; import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import org.apache.cloudstack.vm.schedule.VMScheduledJobVO; +import org.apache.cloudstack.api.ApiCommandResourceType; +import org.apache.cloudstack.schedule.ResourceScheduledJobVO; import org.apache.commons.lang3.time.DateUtils; import org.springframework.stereotype.Component; @@ -31,62 +32,59 @@ import java.util.List; @Component -public class VMScheduledJobDaoImpl extends GenericDaoBase implements VMScheduledJobDao { +public class ResourceScheduledJobDaoImpl extends GenericDaoBase implements ResourceScheduledJobDao { - private final SearchBuilder jobsToStartSearch; + private final SearchBuilder jobsToStartSearch; + private final SearchBuilder expungeJobsBeforeSearch; + private final SearchBuilder expungeJobForScheduleSearch; + private final SearchBuilder scheduleAndTimestampSearch; - private final SearchBuilder expungeJobsBeforeSearch; + static final String SCHEDULED_TIMESTAMP = "scheduledTimestamp"; + static final String SCHEDULE_ID = "scheduleId"; + static final String RESOURCE_TYPE = "resourceType"; - private final SearchBuilder expungeJobForScheduleSearch; - - private final SearchBuilder scheduleAndTimestampSearch; - - static final String SCHEDULED_TIMESTAMP = "scheduled_timestamp"; - - static final String VM_SCHEDULE_ID = "vm_schedule_id"; - - public VMScheduledJobDaoImpl() { + public ResourceScheduledJobDaoImpl() { super(); + jobsToStartSearch = createSearchBuilder(); + jobsToStartSearch.and(RESOURCE_TYPE, jobsToStartSearch.entity().getResourceType(), SearchCriteria.Op.EQ); jobsToStartSearch.and(SCHEDULED_TIMESTAMP, jobsToStartSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ); jobsToStartSearch.and("async_job_id", jobsToStartSearch.entity().getAsyncJobId(), SearchCriteria.Op.NULL); jobsToStartSearch.done(); expungeJobsBeforeSearch = createSearchBuilder(); + expungeJobsBeforeSearch.and(RESOURCE_TYPE, expungeJobsBeforeSearch.entity().getResourceType(), SearchCriteria.Op.EQ); expungeJobsBeforeSearch.and(SCHEDULED_TIMESTAMP, expungeJobsBeforeSearch.entity().getScheduledTime(), SearchCriteria.Op.LT); expungeJobsBeforeSearch.done(); expungeJobForScheduleSearch = createSearchBuilder(); - expungeJobForScheduleSearch.and(VM_SCHEDULE_ID, expungeJobForScheduleSearch.entity().getVmScheduleId(), SearchCriteria.Op.IN); + expungeJobForScheduleSearch.and(SCHEDULE_ID, expungeJobForScheduleSearch.entity().getScheduleId(), SearchCriteria.Op.IN); expungeJobForScheduleSearch.and(SCHEDULED_TIMESTAMP, expungeJobForScheduleSearch.entity().getScheduledTime(), SearchCriteria.Op.GTEQ); expungeJobForScheduleSearch.done(); scheduleAndTimestampSearch = createSearchBuilder(); - scheduleAndTimestampSearch.and(VM_SCHEDULE_ID, scheduleAndTimestampSearch.entity().getVmScheduleId(), SearchCriteria.Op.EQ); + scheduleAndTimestampSearch.and(SCHEDULE_ID, scheduleAndTimestampSearch.entity().getScheduleId(), SearchCriteria.Op.EQ); scheduleAndTimestampSearch.and(SCHEDULED_TIMESTAMP, scheduleAndTimestampSearch.entity().getScheduledTime(), SearchCriteria.Op.EQ); scheduleAndTimestampSearch.done(); } - /** - * Execution of job wouldn't be at exact seconds. So, we round off and then execute. - */ @Override - public List listJobsToStart(Date currentTimestamp) { + public List listJobsToStart(ApiCommandResourceType resourceType, Date currentTimestamp) { if (currentTimestamp == null) { currentTimestamp = new Date(); } Date truncatedTs = DateUtils.round(currentTimestamp, Calendar.MINUTE); - - SearchCriteria sc = jobsToStartSearch.create(); + SearchCriteria sc = jobsToStartSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); sc.setParameters(SCHEDULED_TIMESTAMP, truncatedTs); - Filter filter = new Filter(VMScheduledJobVO.class, "vmScheduleId", true, null, null); + Filter filter = new Filter(ResourceScheduledJobVO.class, "scheduleId", true, null, null); return search(sc, filter); } @Override - public int expungeJobsForSchedules(List vmScheduleIds, Date dateAfter) { - SearchCriteria sc = expungeJobForScheduleSearch.create(); - sc.setParameters(VM_SCHEDULE_ID, vmScheduleIds.toArray()); + public int expungeJobsForSchedules(List scheduleIds, Date dateAfter) { + SearchCriteria sc = expungeJobForScheduleSearch.create(); + sc.setParameters(SCHEDULE_ID, scheduleIds.toArray()); if (dateAfter != null) { sc.setParameters(SCHEDULED_TIMESTAMP, dateAfter); } @@ -94,16 +92,17 @@ public int expungeJobsForSchedules(List vmScheduleIds, Date dateAfter) { } @Override - public int expungeJobsBefore(Date date) { - SearchCriteria sc = expungeJobsBeforeSearch.create(); + public int expungeJobsBefore(ApiCommandResourceType resourceType, Date date) { + SearchCriteria sc = expungeJobsBeforeSearch.create(); + sc.setParameters(RESOURCE_TYPE, resourceType); sc.setParameters(SCHEDULED_TIMESTAMP, date); return expunge(sc); } @Override - public VMScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) { - SearchCriteria sc = scheduleAndTimestampSearch.create(); - sc.setParameters(VM_SCHEDULE_ID, scheduleId); + public ResourceScheduledJobVO findByScheduleAndTimestamp(long scheduleId, Date scheduledTimestamp) { + SearchCriteria sc = scheduleAndTimestampSearch.create(); + sc.setParameters(SCHEDULE_ID, scheduleId); sc.setParameters(SCHEDULED_TIMESTAMP, scheduledTimestamp); return findOneBy(sc); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java index 97e41949a57a..d7e88bd31c3f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ImageStoreDetailsDaoImpl.java @@ -20,6 +20,8 @@ import java.util.List; import java.util.Map; +import javax.inject.Inject; + import org.apache.cloudstack.api.ApiConstants; import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.ConfigKey.Scope; @@ -27,6 +29,8 @@ import org.apache.cloudstack.resourcedetail.ResourceDetailsDaoBase; import org.springframework.stereotype.Component; +import com.cloud.storage.ImageStore; +import com.cloud.utils.Pair; import com.cloud.utils.crypt.DBEncryptionUtil; import com.cloud.utils.db.QueryBuilder; import com.cloud.utils.db.SearchBuilder; @@ -36,6 +40,9 @@ @Component public class ImageStoreDetailsDaoImpl extends ResourceDetailsDaoBase implements ImageStoreDetailsDao, ScopedConfigStorage { + @Inject + ImageStoreDao imageStoreDao; + protected final SearchBuilder storeSearch; public ImageStoreDetailsDaoImpl() { @@ -70,7 +77,7 @@ public Map getDetails(long storeId) { for (ImageStoreDetailVO detail : details) { String name = detail.getName(); String value = detail.getValue(); - if (name.equals(ApiConstants.KEY) || name.equals(ApiConstants.S3_SECRET_KEY)) { + if (name.equals(ApiConstants.KEY) || name.equals(ApiConstants.SECRET_KEY)) { value = DBEncryptionUtil.decrypt(value); } detailsMap.put(name, value); @@ -113,10 +120,20 @@ public String getConfigValue(long id, String key) { public String getConfigValue(long id, ConfigKey key) { ImageStoreDetailVO vo = findDetail(id, key.key()); return vo == null ? null : getActualValue(vo); - } + } @Override public void addDetail(long resourceId, String key, String value, boolean display) { super.addDetail(new ImageStoreDetailVO(resourceId, key, value, display)); } + + @Override + public Pair getParentScope(long id) { + ImageStore store = imageStoreDao.findById(id); + if (store == null) { + return null; + } + return new Pair<>(getScope().getParent(), store.getDataCenterId()); + } + } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDao.java index 94f6b5ec3724..695742823ebe 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDao.java @@ -39,4 +39,6 @@ public interface ObjectStoreDao extends GenericDao { ObjectStoreResponse setObjectStoreResponse(ObjectStoreResponse storeData, ObjectStoreVO store); Integer countAllObjectStores(); + + Boolean updateAllocatedSize(ObjectStoreVO objectStoreVO, long delta); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java index 51abde013b69..891ac0996aca 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreDaoImpl.java @@ -21,6 +21,10 @@ import com.cloud.utils.db.GenericDaoBase; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.Transaction; +import com.cloud.utils.db.TransactionCallback; +import com.cloud.utils.db.TransactionStatus; + import org.apache.cloudstack.api.response.ObjectStoreResponse; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; import org.springframework.stereotype.Component; @@ -142,6 +146,19 @@ public ObjectStoreResponse newObjectStoreResponse(ObjectStoreVO store) { ObjectStoreResponse osResponse = new ObjectStoreResponse(); osResponse.setId(store.getUuid()); osResponse.setName(store.getName()); + if (store.getTotalSize() != null && store.getTotalSize() != 0L) { + osResponse.setStorageTotal(store.getTotalSize()); + } + if (store.getUsedSize() == null) { + osResponse.setStorageUsed(0L); + } else { + osResponse.setStorageUsed(store.getUsedSize()); + } + if (store.getAllocatedSize() == null) { + osResponse.setStorageAllocated(0L); + } else { + osResponse.setStorageAllocated(store.getAllocatedSize()); + } osResponse.setProviderName(store.getProviderName()); String url = store.getUrl(); osResponse.setUrl(url); @@ -159,4 +176,19 @@ public Integer countAllObjectStores() { SearchCriteria sc = createSearchCriteria(); return getCount(sc); } + + @Override + public Boolean updateAllocatedSize(ObjectStoreVO objectStoreVO, long delta) { + return Transaction.execute(new TransactionCallback() { + @Override + public Boolean doInTransaction(final TransactionStatus status) { + if (objectStoreVO.getAllocatedSize() != null) { + objectStoreVO.setAllocatedSize(objectStoreVO.getAllocatedSize() + delta); + } else { + objectStoreVO.setAllocatedSize(delta); + } + return update(objectStoreVO.getId(), objectStoreVO); + } + }); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java index 18cc06a65733..23b650acc79c 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/ObjectStoreVO.java @@ -60,8 +60,11 @@ public class ObjectStoreVO implements ObjectStore { @Column(name = "total_size") private Long totalSize; - @Column(name = "used_bytes") - private Long usedBytes; + @Column(name = "used_size") + private Long usedSize; + + @Column(name = "allocated_size") + private Long allocatedSize; @Transient Map details; @@ -130,18 +133,26 @@ public void setTotalSize(Long totalSize) { this.totalSize = totalSize; } - public Long getUsedBytes() { - return usedBytes; + public Long getUsedSize() { + return usedSize; } - public void setUsedBytes(Long usedBytes) { - this.usedBytes = usedBytes; + public void setUsedSize(Long usedSize) { + this.usedSize = usedSize; } public void setDetails(Map details) { this.details = details; } + public Long getAllocatedSize() { + return allocatedSize; + } + + public void setAllocatedSize(Long allocatedSize) { + this.allocatedSize = allocatedSize; + } + @Override public String toString() { return String.format("ObjectStore %s", diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java index 102df6848f87..57b5f6461c6a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDao.java @@ -58,7 +58,9 @@ public interface PrimaryDataStoreDao extends GenericDao { */ void updateCapacityIops(long id, long capacityIops); - StoragePoolVO persist(StoragePoolVO pool, Map details, List tags, Boolean isTagARule); + StoragePoolVO persist(StoragePoolVO pool, Map details, List tags, Boolean isTagARule, List storageAccessGroups); + + StoragePoolVO persist(StoragePoolVO pool, Map details, List tags, Boolean isTagARule, boolean displayDetails, List storageAccessGroups); /** * Find pool by name. @@ -82,7 +84,9 @@ public interface PrimaryDataStoreDao extends GenericDao { */ List findPoolsByDetails(long dcId, long podId, Long clusterId, Map details, ScopeType scope); - List findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule, long ruleExecuteTimeout); + List findPoolsByTags(long dcId, long podId, Long clusterId, ScopeType scope, String[] tags, boolean validateTagRule, long ruleExecuteTimeout); + + List findPoolsByAccessGroupsForHostConnection(Long dcId, Long podId, Long clusterId, ScopeType scope, String[] storageAccessGroups); List findDisabledPoolsByScope(long dcId, Long podId, Long clusterId, ScopeType scope); @@ -103,6 +107,8 @@ public interface PrimaryDataStoreDao extends GenericDao { void updateDetails(long poolId, Map details); + void removeDetails(long poolId); + Map getDetails(long poolId); List searchForStoragePoolTags(long poolId); @@ -123,6 +129,10 @@ public interface PrimaryDataStoreDao extends GenericDao { List findZoneWideStoragePoolsByTags(long dcId, String[] tags, boolean validateTagRule); + List findZoneWideStoragePoolsByAccessGroupsForHostConnection(long dcId, String[] storageAccessGroups); + + List findZoneWideStoragePoolsByAccessGroupsAndHypervisorTypeForHostConnection(long dcId, String[] storageAccessGroups, HypervisorType type); + List findZoneWideStoragePoolsByHypervisor(long dataCenterId, HypervisorType hypervisorType); List findZoneWideStoragePoolsByHypervisor(long dataCenterId, HypervisorType hypervisorType, String keyword); @@ -139,6 +149,8 @@ public interface PrimaryDataStoreDao extends GenericDao { void deletePoolTags(long poolId); + void deleteStoragePoolAccessGroups(long poolId); + List listChildStoragePoolsInDatastoreCluster(long poolId); Integer countAll(); @@ -150,10 +162,14 @@ public interface PrimaryDataStoreDao extends GenericDao { List listStoragePoolsWithActiveVolumesByOfferingId(long offeringid); Pair, Integer> searchForIdsAndCount(Long storagePoolId, String storagePoolName, Long zoneId, - String path, Long podId, Long clusterId, String address, ScopeType scopeType, StoragePoolStatus status, - String keyword, Filter searchFilter); + String path, Long podId, Long clusterId, Long hostId, String address, ScopeType scopeType, StoragePoolStatus status, + String keyword, String storageAccessGroup, Filter searchFilter); List listByIds(List ids); + List findStoragePoolsByEmptyStorageAccessGroups(Long dcId, Long podId, Long clusterId, ScopeType scope, HypervisorType hypervisorType); + + List findPoolsByStorageTypeAndZone(Storage.StoragePoolType storageType, Long zoneId); + List listByDataCenterIds(List dataCenterIds); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java index 0d901c82306b..b5d6415e3a1a 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/PrimaryDataStoreDaoImpl.java @@ -28,6 +28,8 @@ import javax.inject.Inject; import javax.naming.ConfigurationException; +import com.cloud.storage.StoragePoolAndAccessGroupMapVO; +import com.cloud.storage.dao.StoragePoolAndAccessGroupMapDao; import org.apache.commons.collections.CollectionUtils; import com.cloud.host.Status; @@ -71,15 +73,25 @@ public class PrimaryDataStoreDaoImpl extends GenericDaoBase private StoragePoolHostDao _hostDao; @Inject private StoragePoolTagsDao _tagsDao; + @Inject + StoragePoolAndAccessGroupMapDao _storagePoolAccessGroupMapDao; protected final String DetailsSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_details ON storage_pool.id = storage_pool_details.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and ("; protected final String DetailsSqlSuffix = ") GROUP BY storage_pool_details.pool_id HAVING COUNT(storage_pool_details.name) >= ?"; + protected final String DetailsForHostConnectionSqlSuffix = ") GROUP BY storage_pool_details.pool_id"; private final String ZoneWideTagsSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_tags ON storage_pool.id = storage_pool_tags.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' AND storage_pool_tags.is_tag_a_rule = 0 and storage_pool.data_center_id = ? and storage_pool.scope = ? and ("; private final String ZoneWideTagsSqlSuffix = ") GROUP BY storage_pool_tags.pool_id HAVING COUNT(storage_pool_tags.tag) >= ?"; + private final String ZoneWideStorageAccessGroupsForHostConnectionSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_and_access_group_map ON storage_pool.id = storage_pool_and_access_group_map.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and storage_pool.scope = ? and ("; + private final String ZoneWideStorageAccessGroupsForHostConnectionSqlSuffix = ") GROUP BY storage_pool_and_access_group_map.pool_id"; + private final String ZoneWideStorageAccessGroupsWithHypervisorTypeSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_and_access_group_map ON storage_pool.id = storage_pool_and_access_group_map.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.hypervisor = ? and storage_pool.data_center_id = ? and storage_pool.scope = ? and ("; + private final String ZoneWideStorageAccessGroupsWithHypervisorTypeSqlSuffix = ") GROUP BY storage_pool_and_access_group_map.pool_id"; // Storage tags are now separate from storage_pool_details, leaving only details on that table protected final String TagsSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_tags ON storage_pool.id = storage_pool_tags.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' AND storage_pool_tags.is_tag_a_rule = 0 and storage_pool.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and ("; protected final String TagsSqlSuffix = ") GROUP BY storage_pool_tags.pool_id HAVING COUNT(storage_pool_tags.tag) >= ?"; + protected final String SAGsForHostConnectionSqlPrefix = "SELECT storage_pool.* from storage_pool LEFT JOIN storage_pool_and_access_group_map ON storage_pool.id = storage_pool_and_access_group_map.pool_id WHERE storage_pool.removed is null and storage_pool.status = 'Up' and storage_pool.data_center_id = ? and (storage_pool.pod_id = ? or storage_pool.pod_id is null) and storage_pool.scope = ? and ("; + + protected final String SAGsForHostConnectionSqlSuffix = ") GROUP BY storage_pool_and_access_group_map.pool_id"; private static final String GET_STORAGE_POOLS_OF_VOLUMES_WITHOUT_OR_NOT_HAVING_TAGS = "SELECT s.* " + "FROM volumes vol " + @@ -299,21 +311,32 @@ public StoragePoolVO listById(Integer id) { return findOneIncludingRemovedBy(sc); } + @Override + public StoragePoolVO persist(StoragePoolVO pool, Map details, List tags, Boolean isTagARule, List storageAccessGroups) { + return persist(pool, details, tags, isTagARule, true, storageAccessGroups); + } + @Override @DB - public StoragePoolVO persist(StoragePoolVO pool, Map details, List tags, Boolean isTagARule) { + public StoragePoolVO persist(StoragePoolVO pool, Map details, List tags, Boolean isTagARule, boolean displayDetails, List storageAccessGroups) { TransactionLegacy txn = TransactionLegacy.currentTxn(); txn.start(); pool = super.persist(pool); if (details != null) { for (Map.Entry detail : details.entrySet()) { - StoragePoolDetailVO vo = new StoragePoolDetailVO(pool.getId(), detail.getKey(), detail.getValue(), true); + if (detail.getKey().toLowerCase().contains("password") || detail.getKey().toLowerCase().contains("token")) { + displayDetails = false; + } + StoragePoolDetailVO vo = new StoragePoolDetailVO(pool.getId(), detail.getKey(), detail.getValue(), displayDetails); _detailsDao.persist(vo); } } if (CollectionUtils.isNotEmpty(tags)) { _tagsDao.persist(pool.getId(), tags, isTagARule); } + if (CollectionUtils.isNotEmpty(storageAccessGroups)) { + _storagePoolAccessGroupMapDao.persist(pool.getId(), storageAccessGroups); + } txn.commit(); return pool; } @@ -337,6 +360,13 @@ protected List findPoolsByDetailsOrTagsInternal(long dcId, long p return searchStoragePoolsPreparedStatement(sql, dcId, podId, clusterId, scope, valuesLength); } + protected List findPoolsByDetailsOrTagsForHostConnectionInternal(long dcId, long podId, Long clusterId, ScopeType scope, String sqlValues, ValueType valuesType) { + String sqlPrefix = valuesType.equals(ValueType.DETAILS) ? DetailsSqlPrefix : SAGsForHostConnectionSqlPrefix; + String sqlSuffix = valuesType.equals(ValueType.DETAILS) ? DetailsForHostConnectionSqlSuffix : SAGsForHostConnectionSqlSuffix; + String sql = getSqlPreparedStatement(sqlPrefix, sqlSuffix, sqlValues, clusterId); + return searchStoragePoolsPreparedStatement(sql, dcId, podId, clusterId, scope, null); + } + /** * Search storage pools in a transaction * @param sql prepared statement sql @@ -348,12 +378,13 @@ protected List findPoolsByDetailsOrTagsInternal(long dcId, long p * @return storage pools matching criteria */ @DB - protected List searchStoragePoolsPreparedStatement(String sql, long dcId, Long podId, Long clusterId, ScopeType scope, int valuesLength) { + protected List searchStoragePoolsWithHypervisorTypesPreparedStatement(String sql, HypervisorType type, long dcId, Long podId, Long clusterId, ScopeType scope, Integer valuesLength) { TransactionLegacy txn = TransactionLegacy.currentTxn(); List pools = new ArrayList(); try (PreparedStatement pstmt = txn.prepareStatement(sql);) { if (pstmt != null) { int i = 1; + pstmt.setString(i++, type.toString()); pstmt.setLong(i++, dcId); if (podId != null) { pstmt.setLong(i++, podId); @@ -362,7 +393,51 @@ protected List searchStoragePoolsPreparedStatement(String sql, lo if (clusterId != null) { pstmt.setLong(i++, clusterId); } - pstmt.setInt(i++, valuesLength); + if (valuesLength != null) { + pstmt.setInt(i++, valuesLength); + } + try (ResultSet rs = pstmt.executeQuery();) { + while (rs.next()) { + pools.add(toEntityBean(rs, false)); + } + } catch (SQLException e) { + throw new CloudRuntimeException("Unable to execute :" + e.getMessage(), e); + } + } + } catch (SQLException e) { + throw new CloudRuntimeException("Unable to execute :" + e.getMessage(), e); + } + return pools; + } + + /** + * Search storage pools in a transaction + * @param sql prepared statement sql + * @param dcId data center id + * @param podId pod id + * @param clusterId cluster id + * @param scope scope + * @param valuesLength values length + * @return storage pools matching criteria + */ + @DB + protected List searchStoragePoolsPreparedStatement(String sql, long dcId, Long podId, Long clusterId, ScopeType scope, Integer valuesLength) { + TransactionLegacy txn = TransactionLegacy.currentTxn(); + List pools = new ArrayList(); + try (PreparedStatement pstmt = txn.prepareStatement(sql);) { + if (pstmt != null) { + int i = 1; + pstmt.setLong(i++, dcId); + if (podId != null) { + pstmt.setLong(i++, podId); + } + pstmt.setString(i++, scope.toString()); + if (clusterId != null) { + pstmt.setLong(i++, clusterId); + } + if (valuesLength != null) { + pstmt.setInt(i++, valuesLength); + } try (ResultSet rs = pstmt.executeQuery();) { while (rs.next()) { pools.add(toEntityBean(rs, false)); @@ -419,6 +494,22 @@ protected String getSqlValuesFromStorageTags(String[] tags) throws NullPointerEx return sqlValues.toString(); } + /** + * Return SQL string from storage pool access group map, to be placed between SQL Prefix and SQL Suffix when creating storage tags PreparedStatement. + * @param storageAccessGroups storage tags array + * @return SQL string containing storage tag values to be placed between Prefix and Suffix when creating PreparedStatement. + * @throws NullPointerException if tags is null + * @throws IndexOutOfBoundsException if tags is not null, but empty + */ + protected String getSqlValuesFromStorageAccessGroups(String[] storageAccessGroups) throws NullPointerException, IndexOutOfBoundsException { + StringBuilder sqlValues = new StringBuilder(); + for (String tag : storageAccessGroups) { + sqlValues.append("(storage_pool_and_access_group_map.storage_access_group='").append(tag).append("') OR "); + } + sqlValues.delete(sqlValues.length() - 4, sqlValues.length()); + return sqlValues.toString(); + } + @DB @Override public List findPoolsByDetails(long dcId, long podId, Long clusterId, Map details, ScopeType scope) { @@ -427,10 +518,10 @@ public List findPoolsByDetails(long dcId, long podId, Long cluste } @Override - public List findPoolsByTags(long dcId, long podId, Long clusterId, String[] tags, boolean validateTagRule, long ruleExecuteTimeout) { + public List findPoolsByTags(long dcId, long podId, Long clusterId, ScopeType scope, String[] tags, boolean validateTagRule, long ruleExecuteTimeout) { List storagePools = null; if (tags == null || tags.length == 0) { - storagePools = listBy(dcId, podId, clusterId, ScopeType.CLUSTER); + storagePools = listBy(dcId, podId, clusterId, scope); if (validateTagRule) { storagePools = getPoolsWithoutTagRule(storagePools); @@ -438,7 +529,20 @@ public List findPoolsByTags(long dcId, long podId, Long clusterId } else { String sqlValues = getSqlValuesFromStorageTags(tags); - storagePools = findPoolsByDetailsOrTagsInternal(dcId, podId, clusterId, ScopeType.CLUSTER, sqlValues, ValueType.TAGS, tags.length); + storagePools = findPoolsByDetailsOrTagsInternal(dcId, podId, clusterId, scope, sqlValues, ValueType.TAGS, tags.length); + } + + return storagePools; + } + + @Override + public List findPoolsByAccessGroupsForHostConnection(Long dcId, Long podId, Long clusterId, ScopeType scope, String[] storageAccessGroups) { + List storagePools = null; + if (storageAccessGroups == null || storageAccessGroups.length == 0) { + storagePools = listBy(dcId, podId, clusterId, scope); + } else { + String sqlValues = getSqlValuesFromStorageAccessGroups(storageAccessGroups); + storagePools = findPoolsByDetailsOrTagsForHostConnectionInternal(dcId, podId, clusterId, scope, sqlValues, ValueType.TAGS); } return storagePools; @@ -555,6 +659,77 @@ protected List getPoolsWithoutTagRule(List storage return storagePoolsToReturn; } + @Override + public List findZoneWideStoragePoolsByAccessGroupsForHostConnection(long dcId, String[] storageAccessGroups) { + if (storageAccessGroups == null || storageAccessGroups.length == 0) { + QueryBuilder sc = QueryBuilder.create(StoragePoolVO.class); + sc.and(sc.entity().getDataCenterId(), Op.EQ, dcId); + sc.and(sc.entity().getStatus(), Op.EQ, Status.Up); + sc.and(sc.entity().getScope(), Op.EQ, ScopeType.ZONE); + return sc.list(); + } else { + String sqlValues = getSqlValuesFromStorageAccessGroups(storageAccessGroups); + String sql = getSqlPreparedStatement(ZoneWideStorageAccessGroupsForHostConnectionSqlPrefix, ZoneWideStorageAccessGroupsForHostConnectionSqlSuffix, sqlValues, null); + return searchStoragePoolsPreparedStatement(sql, dcId, null, null, ScopeType.ZONE, null); + } + } + + @Override + public List findZoneWideStoragePoolsByAccessGroupsAndHypervisorTypeForHostConnection(long dcId, String[] storageAccessGroups, HypervisorType type) { + if (storageAccessGroups == null || storageAccessGroups.length == 0) { + QueryBuilder sc = QueryBuilder.create(StoragePoolVO.class); + sc.and(sc.entity().getDataCenterId(), Op.EQ, dcId); + sc.and(sc.entity().getStatus(), Op.EQ, Status.Up); + sc.and(sc.entity().getScope(), Op.EQ, ScopeType.ZONE); + sc.and(sc.entity().getHypervisor(), Op.EQ, type); + return sc.list(); + } else { + String sqlValues = getSqlValuesFromStorageAccessGroups(storageAccessGroups); + String sql = getSqlPreparedStatement(ZoneWideStorageAccessGroupsWithHypervisorTypeSqlPrefix, ZoneWideStorageAccessGroupsWithHypervisorTypeSqlSuffix, sqlValues, null); + return searchStoragePoolsWithHypervisorTypesPreparedStatement(sql, type, dcId, null, null, ScopeType.ZONE, null); + } + } + + @Override + public List findStoragePoolsByEmptyStorageAccessGroups(Long dcId, Long podId, Long clusterId, ScopeType scope, HypervisorType hypervisorType) { + SearchBuilder poolSearch = createSearchBuilder(); + SearchBuilder storageAccessGroupsPoolSearch = _storagePoolAccessGroupMapDao.createSearchBuilder(); + // Set criteria for pools + poolSearch.and("scope", poolSearch.entity().getScope(), Op.EQ); + poolSearch.and("removed", poolSearch.entity().getRemoved(), Op.NULL); + poolSearch.and("status", poolSearch.entity().getStatus(), Op.EQ); + poolSearch.and("datacenterid", poolSearch.entity().getDataCenterId(), Op.EQ); + poolSearch.and("podid", poolSearch.entity().getPodId(), Op.EQ); + poolSearch.and("clusterid", poolSearch.entity().getClusterId(), Op.EQ); + poolSearch.and("hypervisortype", poolSearch.entity().getHypervisor(), Op.EQ); + + // Set StoragePoolAccessGroupMapVO.pool_id IS NULL. This ensures only pools without tags are returned + storageAccessGroupsPoolSearch.and("poolid", storageAccessGroupsPoolSearch.entity().getPoolId(), Op.NULL); + poolSearch.join("tagJoin", storageAccessGroupsPoolSearch, poolSearch.entity().getId(), storageAccessGroupsPoolSearch.entity().getPoolId(), JoinBuilder.JoinType.LEFT); + + SearchCriteria sc = poolSearch.create(); + sc.setParameters("scope", scope.toString()); + sc.setParameters("status", Status.Up.toString()); + + if (dcId != null) { + sc.setParameters("datacenterid", dcId); + } + + if (podId != null) { + sc.setParameters("podid", podId); + } + + if (clusterId != null) { + sc.setParameters("clusterid", clusterId); + } + + if (hypervisorType != null) { + sc.setParameters("hypervisortype", hypervisorType); + } + + return listBy(sc); + } + @Override public List searchForStoragePoolTags(long poolId) { return _tagsDao.getStoragePoolTags(poolId); @@ -574,6 +749,11 @@ public void updateDetails(long poolId, Map details) { } } + @Override + public void removeDetails(long poolId) { + _detailsDao.removeDetails(poolId); + } + @Override public Map getDetails(long poolId) { return _detailsDao.listDetailsKeyPairs(poolId); @@ -653,6 +833,11 @@ public void deletePoolTags(long poolId) { _tagsDao.deleteTags(poolId); } + @Override + public void deleteStoragePoolAccessGroups(long poolId) { + _storagePoolAccessGroupMapDao.deleteStorageAccessGroups(poolId); + } + @Override public List listChildStoragePoolsInDatastoreCluster(long poolId) { QueryBuilder sc = QueryBuilder.create(StoragePoolVO.class); @@ -719,9 +904,10 @@ public List listStoragePoolsWithActiveVolumesByOfferingId(long of @Override public Pair, Integer> searchForIdsAndCount(Long storagePoolId, String storagePoolName, Long zoneId, - String path, Long podId, Long clusterId, String address, ScopeType scopeType, StoragePoolStatus status, - String keyword, Filter searchFilter) { - SearchCriteria sc = createStoragePoolSearchCriteria(storagePoolId, storagePoolName, zoneId, path, podId, clusterId, address, scopeType, status, keyword); + String path, Long podId, Long clusterId, Long hostId, String address, ScopeType scopeType, StoragePoolStatus status, + String keyword, String storageAccessGroup, Filter searchFilter) { + SearchCriteria sc = createStoragePoolSearchCriteria(storagePoolId, storagePoolName, zoneId, path, podId, clusterId, + hostId, address, scopeType, status, keyword, storageAccessGroup); Pair, Integer> uniquePair = searchAndCount(sc, searchFilter); List idList = uniquePair.first().stream().map(StoragePoolVO::getId).collect(Collectors.toList()); return new Pair<>(idList, uniquePair.second()); @@ -737,6 +923,14 @@ public List listByIds(List ids) { return listBy(sc); } + @Override + public List findPoolsByStorageTypeAndZone(Storage.StoragePoolType storageType, Long zoneId) { + SearchCriteria sc = AllFieldSearch.create(); + sc.setParameters("poolType", storageType); + sc.addAnd("dataCenterId", Op.EQ, zoneId); + return listBy(sc); + } + @Override public List listByDataCenterIds(List dataCenterIds) { if (CollectionUtils.isEmpty(dataCenterIds)) { @@ -748,8 +942,8 @@ public List listByDataCenterIds(List dataCenterIds) { } private SearchCriteria createStoragePoolSearchCriteria(Long storagePoolId, String storagePoolName, - Long zoneId, String path, Long podId, Long clusterId, String address, ScopeType scopeType, - StoragePoolStatus status, String keyword) { + Long zoneId, String path, Long podId, Long clusterId, Long hostId, String address, ScopeType scopeType, + StoragePoolStatus status, String keyword, String storageAccessGroup) { SearchBuilder sb = createSearchBuilder(); sb.select(null, SearchCriteria.Func.DISTINCT, sb.entity().getId()); // select distinct // ids @@ -764,6 +958,18 @@ private SearchCriteria createStoragePoolSearchCriteria(Long stora sb.and("status", sb.entity().getStatus(), SearchCriteria.Op.EQ); sb.and("parent", sb.entity().getParent(), SearchCriteria.Op.EQ); + if (hostId != null) { + SearchBuilder hostJoin = _hostDao.createSearchBuilder(); + hostJoin.and("hostId", hostJoin.entity().getHostId(), SearchCriteria.Op.EQ); + sb.join("poolHostJoin", hostJoin, sb.entity().getId(), hostJoin.entity().getPoolId(), JoinBuilder.JoinType.INNER); + } + + if (storageAccessGroup != null) { + SearchBuilder storageAccessGroupJoin = _storagePoolAccessGroupMapDao.createSearchBuilder(); + storageAccessGroupJoin.and("storageAccessGroup", storageAccessGroupJoin.entity().getStorageAccessGroup(), SearchCriteria.Op.EQ); + sb.join("poolStorageAccessGroupJoin", storageAccessGroupJoin, sb.entity().getId(), storageAccessGroupJoin.entity().getPoolId(), JoinBuilder.JoinType.INNER); + } + SearchCriteria sc = sb.create(); if (keyword != null) { @@ -812,6 +1018,15 @@ private SearchCriteria createStoragePoolSearchCriteria(Long stora sc.setParameters("status", status.toString()); } sc.setParameters("parent", 0); + + if (hostId != null) { + sc.setJoinParameters("poolHostJoin", "hostId", hostId); + } + + if (storageAccessGroup != null) { + sc.setJoinParameters("poolStorageAccessGroupJoin", "storageAccessGroup", storageAccessGroup); + } + return sc; } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java index 96df49287736..2d337fe07fc7 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDao.java @@ -19,6 +19,7 @@ import java.util.Date; import java.util.List; +import com.cloud.hypervisor.Hypervisor; import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; @@ -46,12 +47,33 @@ public interface SnapshotDataStoreDao extends GenericDao listBySnapshot(long snapshotId, DataStoreRole role); + SnapshotDataStoreVO findParent(DataStoreRole role, Long storeId, Long zoneId, Long volumeId, boolean kvmIncrementalSnapshot, Hypervisor.HypervisorType hypervisorType); + + SnapshotDataStoreVO findBySnapshotIdAndDataStoreRoleAndState(long snapshotId, DataStoreRole role, ObjectInDataStoreStateMachine.State state); + + List listBySnapshotIdAndDataStoreRoleAndStateIn(long snapshotId, DataStoreRole role, ObjectInDataStoreStateMachine.State... state); + + List listReadyByVolumeIdAndCheckpointPathNotNull(long volumeId); + + SnapshotDataStoreVO findOneBySnapshotId(long snapshotId, long zoneId); + + List listBySnapshotId(long snapshotId); + + List listBySnapshotAndDataStoreRole(long snapshotId, DataStoreRole role); + + List listExtractedSnapshotsBeforeDate(Date beforeDate); + + List listSnapshotsBySnapshotId(long snapshotId); List listReadyBySnapshot(long snapshotId, DataStoreRole role); + List listReadyBySnapshotId(long snapshotId); SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole role); + List findBySnapshotIdAndNotInDestroyedHiddenState(long snapshotId); + + SnapshotDataStoreVO findBySnapshotIdInAnyState(long snapshotId, DataStoreRole role); + List listDestroyed(long storeId); List findBySnapshotId(long snapshotId); @@ -77,6 +99,8 @@ public interface SnapshotDataStoreDao extends GenericDao findByVolume(long snapshotId, long volumeId, DataStoreRole role); + void expungeBySnapshotIdAndStoreRole(long snapshotId, DataStoreRole role); + /** * List all snapshots in 'snapshot_store_ref' by volume and data store role. Therefore, it is possible to list all snapshots that are in the primary storage or in the secondary storage. */ diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java index c68316dd1feb..cb88e21cf9d6 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreDaoImpl.java @@ -16,24 +16,6 @@ // under the License. package org.apache.cloudstack.storage.datastore.db; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; -import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; -import org.apache.commons.collections.CollectionUtils; -import org.springframework.stereotype.Component; - import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.DataStoreRole; import com.cloud.storage.SnapshotVO; @@ -47,6 +29,25 @@ import com.cloud.utils.db.TransactionLegacy; import com.cloud.utils.db.UpdateBuilder; +import org.apache.cloudstack.engine.subsystem.api.storage.DataObjectInStore; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State; + +import org.apache.commons.collections.CollectionUtils; + +import org.springframework.stereotype.Component; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collections; +import java.util.Date; +import java.util.List; +import java.util.Map; + @Component public class SnapshotDataStoreDaoImpl extends GenericDaoBase implements SnapshotDataStoreDao { private static final String STORE_ID = "store_id"; @@ -58,26 +59,46 @@ public class SnapshotDataStoreDaoImpl extends GenericDaoBase searchFilteringStoreIdEqStoreRoleEqStateNeqRefCntNeq; protected SearchBuilder searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq; private SearchBuilder stateSearch; - private SearchBuilder idStateNeqSearch; + private SearchBuilder idStateNinSearch; + private SearchBuilder idEqRoleEqStateInSearch; protected SearchBuilder snapshotVOSearch; private SearchBuilder snapshotCreatedSearch; private SearchBuilder dataStoreAndInstallPathSearch; private SearchBuilder storeAndSnapshotIdsSearch; private SearchBuilder storeSnapshotDownloadStatusSearch; + private SearchBuilder searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull; + private SearchBuilder searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore; + private SearchBuilder searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq; + + private SearchBuilder searchFilteringVolumeIdAndStateAndCreatedAfter; + + private SearchBuilder searchBySnapshotId; protected static final List HYPERVISORS_SUPPORTING_SNAPSHOTS_CHAINING = List.of(Hypervisor.HypervisorType.XenServer); @Inject protected SnapshotDao snapshotDao; + @Inject + protected ImageStoreDao imageStoreDao; + private static final String FIND_OLDEST_OR_LATEST_SNAPSHOT = "select store_id, store_role, snapshot_id from cloud.snapshot_store_ref where " + " store_role = ? and volume_id = ? and state = 'Ready'" + " order by created %s " + " limit 1"; + private static final String FIND_SNAPSHOT_IN_ZONE = "SELECT ssr.* FROM " + + "snapshot_store_ref ssr, snapshots s " + + "WHERE ssr.snapshot_id=? AND ssr.snapshot_id = s.id AND s.data_center_id=?;"; + private static final String GET_PHYSICAL_SIZE_OF_SNAPSHOTS_ON_PRIMARY_BY_ACCOUNT = "SELECT SUM(s.physical_size) " + "FROM cloud.snapshot_store_ref s " + "LEFT JOIN cloud.snapshots ON s.snapshot_id = snapshots.id " + @@ -127,10 +148,16 @@ public boolean configure(String name, Map params) throws Configu stateSearch.and(STATE, stateSearch.entity().getState(), SearchCriteria.Op.IN); stateSearch.done(); - idStateNeqSearch = createSearchBuilder(); - idStateNeqSearch.and(SNAPSHOT_ID, idStateNeqSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ); - idStateNeqSearch.and(STATE, idStateNeqSearch.entity().getState(), SearchCriteria.Op.NEQ); - idStateNeqSearch.done(); + + idStateNinSearch = createSearchBuilder(); + idStateNinSearch.and(SNAPSHOT_ID, idStateNinSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ); + idStateNinSearch.and(STATE, idStateNinSearch.entity().getState(), SearchCriteria.Op.NOTIN); + idStateNinSearch.done(); + + idEqRoleEqStateInSearch = createSearchBuilder(); + idEqRoleEqStateInSearch.and(SNAPSHOT_ID, idEqRoleEqStateInSearch.entity().getSnapshotId(), SearchCriteria.Op.EQ); + idEqRoleEqStateInSearch.and(STORE_ROLE, idEqRoleEqStateInSearch.entity().getRole(), SearchCriteria.Op.EQ); + idEqRoleEqStateInSearch.and(STATE, idEqRoleEqStateInSearch.entity().getState(), SearchCriteria.Op.IN); snapshotVOSearch = snapshotDao.createSearchBuilder(); snapshotVOSearch.and(VOLUME_ID, snapshotVOSearch.entity().getVolumeId(), SearchCriteria.Op.EQ); @@ -159,6 +186,38 @@ public boolean configure(String name, Map params) throws Configu storeSnapshotDownloadStatusSearch.and("downloadState", storeSnapshotDownloadStatusSearch.entity().getDownloadState(), SearchCriteria.Op.IN); storeSnapshotDownloadStatusSearch.done(); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull = createSearchBuilder(); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.and(VOLUME_ID, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.entity().getVolumeId(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.and(STATE, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.entity().getState(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.and(STORE_ROLE, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.entity().getRole(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.and(KVM_CHECKPOINT_PATH, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.entity().getKvmCheckpointPath(), SearchCriteria.Op.NNULL); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.and(STORE_ID, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.entity().getDataStoreId(), SearchCriteria.Op.IN); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.done(); + + searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore = createSearchBuilder(); + searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.and(STATE, searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.entity().getState(), SearchCriteria.Op.EQ); + searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.and(DOWNLOAD_URL, searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.entity().getExtractUrl(), SearchCriteria.Op.NNULL); + searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.and(URL_CREATED_BEFORE, searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.entity().getExtractUrlCreated(), SearchCriteria.Op.LT); + searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.done(); + + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq = createSearchBuilder(); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.and(STATE, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.entity().getState(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.and(VOLUME_ID, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.entity().getVolumeId(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.and(STORE_ROLE, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.entity().getRole(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.and(STORE_ID, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.entity().getDataStoreId(), SearchCriteria.Op.IN); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.and(INSTALL_PATH, searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.entity().getInstallPath(), SearchCriteria.Op.EQ); + searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.done(); + + searchBySnapshotId = createSearchBuilder(); + searchBySnapshotId.and(SNAPSHOT_ID, searchBySnapshotId.entity().getSnapshotId(), SearchCriteria.Op.EQ); + searchBySnapshotId.and(STATE, searchBySnapshotId.entity().getState(), SearchCriteria.Op.EQ); + searchBySnapshotId.done(); + + searchFilteringVolumeIdAndStateAndCreatedAfter = createSearchBuilder(); + searchFilteringVolumeIdAndStateAndCreatedAfter.and(STATE, searchFilteringVolumeIdAndStateAndCreatedAfter.entity().getState(), SearchCriteria.Op.EQ); + searchFilteringVolumeIdAndStateAndCreatedAfter.and(VOLUME_ID, searchFilteringVolumeIdAndStateAndCreatedAfter.entity().getVolumeId(), SearchCriteria.Op.EQ); + searchFilteringVolumeIdAndStateAndCreatedAfter.and(CREATED, searchFilteringVolumeIdAndStateAndCreatedAfter.entity().getCreated(), SearchCriteria.Op.GT); + searchFilteringVolumeIdAndStateAndCreatedAfter.done(); return true; } @@ -291,30 +350,106 @@ protected SnapshotDataStoreVO findOldestOrLatestSnapshotForVolume(long volumeId, @Override @DB public SnapshotDataStoreVO findParent(DataStoreRole role, Long storeId, Long volumeId) { - if (!isSnapshotChainingRequired(volumeId)) { + return findParent(role, storeId, null, volumeId, false, null); + } + + @Override + @DB + public SnapshotDataStoreVO findParent(DataStoreRole role, Long storeId, Long zoneId, Long volumeId, boolean kvmIncrementalSnapshot, Hypervisor.HypervisorType hypervisorType) { + if (!isSnapshotChainingRequired(volumeId, kvmIncrementalSnapshot)) { logger.trace(String.format("Snapshot chaining is not required for snapshots of volume [%s]. Returning null as parent.", volumeId)); return null; } - SearchCriteria sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create(); + SearchCriteria sc; + if (kvmIncrementalSnapshot && Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + sc = searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.create(); + } else { + sc = searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqPathEq.create(); + } + sc.setParameters(VOLUME_ID, volumeId); - sc.setParameters(STORE_ROLE, role.toString()); + if (role != null) { + sc.setParameters(STORE_ROLE, role.toString()); + } sc.setParameters(STATE, ObjectInDataStoreStateMachine.State.Ready.name()); - sc.setParameters(STORE_ID, storeId); + if (storeId != null) { + sc.setParameters(STORE_ID, new Long[]{storeId}); + } else if (zoneId != null) { + List imageStores = imageStoreDao.listStoresByZoneId(zoneId); + Object[] imageStoreIds = imageStores.stream().map(ImageStoreVO::getId).toArray(); + sc.setParameters(STORE_ID, imageStoreIds); + } List snapshotList = listBy(sc, new Filter(SnapshotDataStoreVO.class, CREATED, false, null, null)); - if (CollectionUtils.isNotEmpty(snapshotList)) { - return snapshotList.get(0); + if (CollectionUtils.isEmpty(snapshotList)) { + return null; + } + + SnapshotDataStoreVO parent = snapshotList.get(0); + + if (kvmIncrementalSnapshot && parent.getKvmCheckpointPath() == null && Hypervisor.HypervisorType.KVM.equals(hypervisorType)) { + return null; + } + + return parent; + } + + @Override + public SnapshotDataStoreVO findBySnapshotIdAndDataStoreRoleAndState(long snapshotId, DataStoreRole role, State state) { + SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); + sc.setParameters(STATE, state); + return findOneBy(sc); + } + + @Override + public List listBySnapshotIdAndDataStoreRoleAndStateIn(long snapshotId, DataStoreRole role, State... state) { + SearchCriteria sc = idEqRoleEqStateInSearch.create(); + sc.setParameters(SNAPSHOT_ID, snapshotId); + sc.setParameters(STORE_ROLE, role); + sc.setParameters(STATE, (Object[])state); + return listBy(sc); + } + + @Override + public SnapshotDataStoreVO findOneBySnapshotId(long snapshotId, long zoneId) { + try (TransactionLegacy transactionLegacy = TransactionLegacy.currentTxn()) { + try (PreparedStatement preparedStatement = transactionLegacy.prepareStatement(FIND_SNAPSHOT_IN_ZONE)) { + preparedStatement.setLong(1, snapshotId); + preparedStatement.setLong(2, zoneId); + + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + return toEntityBean(resultSet, false); + } + } + } + } catch (SQLException e) { + logger.warn(String.format("Failed to find %s snapshot in zone %s due to [%s].", snapshotId, zoneId, e.getMessage()), e); } return null; } @Override - public List listBySnapshot(long snapshotId, DataStoreRole role) { + public List listBySnapshotId(long snapshotId) { + SearchCriteria sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create(); + sc.setParameters(SNAPSHOT_ID, snapshotId); + return listBy(sc); + } + + @Override + public List listBySnapshotAndDataStoreRole(long snapshotId, DataStoreRole role) { SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); return listBy(sc); } + @Override + public List listSnapshotsBySnapshotId(long snapshotId) { + SearchCriteria sc = searchBySnapshotId.create(); + sc.setParameters(SNAPSHOT_ID, snapshotId); + return listBy(sc); + } + @Override public List listReadyBySnapshot(long snapshotId, DataStoreRole role) { SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); @@ -322,6 +457,14 @@ public List listReadyBySnapshot(long snapshotId, DataStoreR return listBy(sc); } + @Override + public List listReadyBySnapshotId(long snapshotId) { + SearchCriteria sc = searchBySnapshotId.create(); + sc.setParameters(SNAPSHOT_ID, snapshotId); + sc.setParameters(STATE, State.Ready); + return listBy(sc); + } + @Override public SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole role) { SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); @@ -329,6 +472,18 @@ public SnapshotDataStoreVO findBySourceSnapshot(long snapshotId, DataStoreRole r return findOneBy(sc); } + @Override + public SnapshotDataStoreVO findBySnapshotIdInAnyState(long snapshotId, DataStoreRole role) { + SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); + return findOneBy(sc); + } + + @Override + public void expungeBySnapshotIdAndStoreRole(long snapshotId, DataStoreRole role) { + SearchCriteria sc = createSearchCriteriaBySnapshotIdAndStoreRole(snapshotId, role); + expunge(sc); + } + @Override public List listAllByVolumeAndDataStore(long volumeId, DataStoreRole role) { SearchCriteria sc = searchFilteringStoreIdEqStateEqStoreRoleEqIdEqUpdateCountEqSnapshotIdEqVolumeIdEq.create(); @@ -355,9 +510,17 @@ public List findBySnapshotId(long snapshotId) { @Override public List findBySnapshotIdWithNonDestroyedState(long snapshotId) { - SearchCriteria sc = idStateNeqSearch.create(); + SearchCriteria sc = idStateNinSearch.create(); sc.setParameters(SNAPSHOT_ID, snapshotId); - sc.setParameters(STATE, State.Destroyed); + sc.setParameters(STATE, State.Destroyed.name()); + return listBy(sc); + } + + @Override + public List findBySnapshotIdAndNotInDestroyedHiddenState(long snapshotId) { + SearchCriteria sc = idStateNinSearch.create(); + sc.setParameters(SNAPSHOT_ID, snapshotId); + sc.setParameters(STATE, State.Destroyed.name(), State.Hidden.name()); return listBy(sc); } @@ -500,13 +663,35 @@ protected SearchCriteria createSearchCriteriaBySnapshotIdAn return sc; } - protected boolean isSnapshotChainingRequired(long volumeId) { + protected boolean isSnapshotChainingRequired(long volumeId, boolean kvmIncrementalSnapshot) { SearchCriteria sc = snapshotVOSearch.create(); sc.setParameters(VOLUME_ID, volumeId); SnapshotVO snapshot = snapshotDao.findOneBy(sc); - return snapshot != null && HYPERVISORS_SUPPORTING_SNAPSHOTS_CHAINING.contains(snapshot.getHypervisorType()); + if (snapshot == null) { + return false; + } + + Hypervisor.HypervisorType hypervisorType = snapshot.getHypervisorType(); + return HYPERVISORS_SUPPORTING_SNAPSHOTS_CHAINING.contains(hypervisorType) || (Hypervisor.HypervisorType.KVM.equals(hypervisorType) && kvmIncrementalSnapshot); + } + + @Override + public List listReadyByVolumeIdAndCheckpointPathNotNull(long volumeId) { + SearchCriteria sc = searchFilteringStoreIdInVolumeIdEqStoreRoleEqStateEqKVMCheckpointNotNull.create(); + sc.setParameters(VOLUME_ID, volumeId); + sc.setParameters(STATE, State.Ready); + return listBy(sc); + } + + @Override + public List listExtractedSnapshotsBeforeDate(Date beforeDate) { + SearchCriteria sc = searchFilterStateAndDownloadUrlNotNullAndDownloadUrlCreatedBefore.create(); + sc.setParameters(URL_CREATED_BEFORE, beforeDate); + sc.setParameters(STATE, State.Ready); + + return listBy(sc); } @Override diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java index 7a466c1f5055..44eb7e6c02cb 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/SnapshotDataStoreVO.java @@ -29,6 +29,8 @@ import javax.persistence.Temporal; import javax.persistence.TemporalType; +import org.apache.cloudstack.utils.reflectiontostringbuilderutils.ReflectionToStringBuilderUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; @@ -80,12 +82,18 @@ public class SnapshotDataStoreVO implements StateObject, StateDao listSharedFSToBeDestroyed(Date date); SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, Long domainId); + + SharedFSVO findByVm(long vmId); } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java index da6220716715..dd23787f982f 100644 --- a/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java +++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/sharedfs/dao/SharedFSDaoImpl.java @@ -114,4 +114,14 @@ public SharedFSVO findSharedFSByNameAccountDomain(String name, Long accountId, L sc.setParameters("domainId", domainId); return findOneBy(sc); } + + @Override + public SharedFSVO findByVm(long vmId) { + SearchBuilder sb = createSearchBuilder(); + sb.and("vmId", sb.entity().getVmId(), SearchCriteria.Op.EQ); + sb.done(); + SearchCriteria sc = sb.create(); + sc.setParameters("vmId", vmId); + return findOneBy(sc); + } } diff --git a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.java b/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.java deleted file mode 100644 index db8c0c3068f8..000000000000 --- a/engine/schema/src/main/java/org/apache/cloudstack/vm/schedule/dao/VMScheduleDaoImpl.java +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -package org.apache.cloudstack.vm.schedule.dao; - -import com.cloud.utils.Pair; -import com.cloud.utils.db.Filter; -import com.cloud.utils.db.GenericDaoBase; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; -import org.apache.cloudstack.api.ApiConstants; -import org.apache.cloudstack.vm.schedule.VMSchedule; -import org.apache.cloudstack.vm.schedule.VMScheduleVO; -import org.springframework.stereotype.Component; - -import java.util.Date; -import java.util.List; - -@Component -public class VMScheduleDaoImpl extends GenericDaoBase implements VMScheduleDao { - - private final SearchBuilder activeScheduleSearch; - - private final SearchBuilder scheduleSearchByVmIdAndIds; - - private final SearchBuilder scheduleSearch; - - public VMScheduleDaoImpl() { - super(); - activeScheduleSearch = createSearchBuilder(); - activeScheduleSearch.and(ApiConstants.ENABLED, activeScheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ); - activeScheduleSearch.and().op(activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.NULL); - activeScheduleSearch.or(ApiConstants.END_DATE, activeScheduleSearch.entity().getEndDate(), SearchCriteria.Op.GT); - activeScheduleSearch.cp(); - activeScheduleSearch.done(); - - scheduleSearchByVmIdAndIds = createSearchBuilder(); - scheduleSearchByVmIdAndIds.and(ApiConstants.ID, scheduleSearchByVmIdAndIds.entity().getId(), SearchCriteria.Op.IN); - scheduleSearchByVmIdAndIds.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearchByVmIdAndIds.entity().getVmId(), SearchCriteria.Op.EQ); - scheduleSearchByVmIdAndIds.done(); - - scheduleSearch = createSearchBuilder(); - scheduleSearch.and(ApiConstants.ID, scheduleSearch.entity().getId(), SearchCriteria.Op.EQ); - scheduleSearch.and(ApiConstants.VIRTUAL_MACHINE_ID, scheduleSearch.entity().getVmId(), SearchCriteria.Op.EQ); - scheduleSearch.and(ApiConstants.ACTION, scheduleSearch.entity().getAction(), SearchCriteria.Op.EQ); - scheduleSearch.and(ApiConstants.ENABLED, scheduleSearch.entity().getEnabled(), SearchCriteria.Op.EQ); - scheduleSearch.done(); - - } - - @Override - public List listAllActiveSchedules() { - // WHERE enabled = true AND (end_date IS NULL OR end_date > current_date) - SearchCriteria sc = activeScheduleSearch.create(); - sc.setParameters(ApiConstants.ENABLED, true); - sc.setParameters(ApiConstants.END_DATE, new Date()); - return search(sc, null); - } - - @Override - public long removeSchedulesForVmIdAndIds(Long vmId, List ids) { - SearchCriteria sc = scheduleSearchByVmIdAndIds.create(); - sc.setParameters(ApiConstants.ID, ids.toArray()); - sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId); - return remove(sc); - } - - @Override - public Pair, Integer> searchAndCount(Long id, Long vmId, VMSchedule.Action action, Boolean enabled, Long offset, Long limit) { - SearchCriteria sc = scheduleSearch.create(); - - if (id != null) { - sc.setParameters(ApiConstants.ID, id); - } - if (enabled != null) { - sc.setParameters(ApiConstants.ENABLED, enabled); - } - if (action != null) { - sc.setParameters(ApiConstants.ACTION, action); - } - sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId); - - Filter filter = new Filter(VMScheduleVO.class, ApiConstants.ID, false, offset, limit); - return searchAndCount(sc, filter); - } - - @Override - public SearchCriteria getSearchCriteriaForVMId(Long vmId) { - SearchCriteria sc = scheduleSearch.create(); - sc.setParameters(ApiConstants.VIRTUAL_MACHINE_ID, vmId); - return sc; - } -} diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml index d6d72f9228e1..a0dada545617 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-common-daos-between-management-and-usage-context.xml @@ -46,6 +46,7 @@ + @@ -62,7 +63,8 @@ - + + @@ -70,6 +72,9 @@ - + + + + diff --git a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml index 4f22234d7bf4..932db538f30b 100644 --- a/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml +++ b/engine/schema/src/main/resources/META-INF/cloudstack/core/spring-engine-schema-core-daos-context.xml @@ -108,6 +108,7 @@ + @@ -116,6 +117,7 @@ + @@ -138,6 +140,7 @@ + @@ -235,13 +238,11 @@ - - @@ -270,7 +271,13 @@ + + + + + + @@ -282,8 +289,9 @@ - - + + + @@ -300,4 +308,26 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_drop_column.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_drop_column.sql new file mode 100644 index 000000000000..31d5b9f500f0 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_drop_column.sql @@ -0,0 +1,28 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- in cloud +DROP PROCEDURE IF EXISTS `cloud`.`IDEMPOTENT_DROP_COLUMN`; + +-- Error 1091: Can't DROP column; check that column/key exists +CREATE PROCEDURE `cloud`.`IDEMPOTENT_DROP_COLUMN` ( + IN in_table_name VARCHAR(200), + IN in_column_name VARCHAR(200) +) +BEGIN + + DECLARE CONTINUE HANDLER FOR 1091 BEGIN END; SET @ddl = CONCAT('ALTER TABLE ', in_table_name, ' DROP COLUMN ', in_column_name); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_drop_unique_key.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_drop_unique_key.sql new file mode 100644 index 000000000000..fdcca6fb4089 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.idempotent_drop_unique_key.sql @@ -0,0 +1,26 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- in cloud +DROP PROCEDURE IF EXISTS `cloud`.`IDEMPOTENT_DROP_UNIQUE_KEY`; + +CREATE PROCEDURE `cloud`.`IDEMPOTENT_DROP_UNIQUE_KEY` ( + IN in_table_name VARCHAR(200), + IN in_index_name VARCHAR(200) +) +BEGIN + DECLARE CONTINUE HANDLER FOR 1091, 1025 BEGIN END; SET @ddl = CONCAT('ALTER TABLE ', in_table_name, ' DROP KEY ', in_index_name); PREPARE stmt FROM @ddl; EXECUTE stmt; DEALLOCATE PREPARE stmt; END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_category_if_not_exists.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_category_if_not_exists.sql new file mode 100644 index 000000000000..a82dc7204c2e --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_category_if_not_exists.sql @@ -0,0 +1,27 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- Add new OS categories if not present +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`; +CREATE PROCEDURE `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`(IN os_name VARCHAR(255)) +BEGIN + IF NOT EXISTS ((SELECT 1 FROM `cloud`.`guest_os_category` WHERE name = os_name)) + THEN + INSERT INTO `cloud`.`guest_os_category` (name, uuid) + VALUES (os_name, UUID()) +; END IF +; END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_custom_action_details_if_not_exists.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_custom_action_details_if_not_exists.sql new file mode 100644 index 000000000000..77b162236266 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_custom_action_details_if_not_exists.sql @@ -0,0 +1,46 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS` ( + IN ext_name VARCHAR(255), + IN action_name VARCHAR(255), + IN param_json TEXT +) +BEGIN + DECLARE action_id BIGINT UNSIGNED +; SELECT `eca`.`id` INTO action_id FROM `cloud`.`extension_custom_action` `eca` + JOIN `cloud`.`extension` `e` ON `e`.`id` = `eca`.`extension_id` + WHERE `eca`.`name` = action_name AND `e`.`name` = ext_name LIMIT 1 +; IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension_custom_action_details` + WHERE `extension_custom_action_id` = action_id + AND `name` = 'parameters' + ) THEN + INSERT INTO `cloud`.`extension_custom_action_details` ( + `extension_custom_action_id`, + `name`, + `value`, + `display` + ) VALUES ( + action_id, + 'parameters', + param_json, + 0 + ) +; END IF +;END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_custom_action_if_not_exists.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_custom_action_if_not_exists.sql new file mode 100644 index 000000000000..9dbffa630f84 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_custom_action_if_not_exists.sql @@ -0,0 +1,46 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`( + IN ext_name VARCHAR(255), + IN action_name VARCHAR(255), + IN action_desc VARCHAR(4096), + IN resource_type VARCHAR(255), + IN allowed_roles INT UNSIGNED, + IN success_msg VARCHAR(4096), + IN error_msg VARCHAR(4096), + IN timeout_seconds INT UNSIGNED +) +BEGIN + DECLARE ext_id BIGINT +; SELECT `id` INTO ext_id FROM `cloud`.`extension` WHERE `name` = ext_name LIMIT 1 +; IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension_custom_action` WHERE `name` = action_name AND `extension_id` = ext_id + ) THEN + INSERT INTO `cloud`.`extension_custom_action` ( + `uuid`, `name`, `description`, `extension_id`, `resource_type`, + `allowed_role_types`, `success_message`, `error_message`, + `enabled`, `timeout`, `created`, `removed` + ) + VALUES ( + UUID(), action_name, action_desc, ext_id, resource_type, + allowed_roles, success_msg, error_msg, + 1, timeout_seconds, NOW(), NULL + ) +; END IF +;END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_detail_if_not_exists.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_detail_if_not_exists.sql new file mode 100644 index 000000000000..f9d6c5da9512 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_detail_if_not_exists.sql @@ -0,0 +1,39 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`( + IN ext_name VARCHAR(255), + IN detail_key VARCHAR(255), + IN detail_value TEXT, + IN display TINYINT(1) +) +BEGIN + DECLARE ext_id BIGINT +; SELECT `id` INTO ext_id FROM `cloud`.`extension` WHERE `name` = ext_name LIMIT 1 +; IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension_details` + WHERE `extension_id` = ext_id AND `name` = detail_key + ) THEN + INSERT INTO `cloud`.`extension_details` ( + `extension_id`, `name`, `value`, `display` + ) + VALUES ( + ext_id, detail_key, detail_value, display + ) +; END IF +;END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_if_not_exists.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_if_not_exists.sql new file mode 100644 index 000000000000..8d74f9b2a986 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.insert_extension_if_not_exists.sql @@ -0,0 +1,38 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`( + IN ext_name VARCHAR(255), + IN ext_desc VARCHAR(255), + IN ext_path VARCHAR(255) +) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension` WHERE `name` = ext_name + ) THEN + INSERT INTO `cloud`.`extension` ( + `uuid`, `name`, `description`, `type`, + `relative_path`, `path_ready`, + `is_user_defined`, `state`, `created`, `removed` + ) + VALUES ( + UUID(), ext_name, ext_desc, 'Orchestrator', + ext_path, 1, 0, 'Enabled', NOW(), NULL + ) +; END IF +;END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.update_category_for_guest_oses.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.update_category_for_guest_oses.sql new file mode 100644 index 000000000000..87f3a85d27ef --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.update_category_for_guest_oses.sql @@ -0,0 +1,33 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- Move existing guest OS to new categories +DROP PROCEDURE IF EXISTS `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`; +CREATE PROCEDURE `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`(IN category_name VARCHAR(255), IN os_name VARCHAR(255)) +BEGIN + DECLARE category_id BIGINT +; SELECT `id` INTO category_id + FROM `cloud`.`guest_os_category` + WHERE `name` = category_name + LIMIT 1 +; IF category_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Category not found' +; END IF +; UPDATE `cloud`.`guest_os` + SET `category_id` = category_id + WHERE `display_name` LIKE CONCAT('%', os_name, '%') +; END; diff --git a/engine/schema/src/main/resources/META-INF/db/procedures/cloud.update_new_and_delete_old_category_for_guest_os.sql b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.update_new_and_delete_old_category_for_guest_os.sql new file mode 100644 index 000000000000..42f7aa738cff --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/procedures/cloud.update_new_and_delete_old_category_for_guest_os.sql @@ -0,0 +1,35 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- Move existing guest OS whose category will be deleted to Other category +DROP PROCEDURE IF EXISTS `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`; +CREATE PROCEDURE `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`(IN to_category_name VARCHAR(255), IN from_category_name VARCHAR(255)) +BEGIN + DECLARE done INT DEFAULT 0 +; DECLARE to_category_id BIGINT +; SELECT id INTO to_category_id + FROM `cloud`.`guest_os_category` + WHERE `name` = to_category_name + LIMIT 1 +; IF to_category_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'ToCategory not found' +; END IF +; UPDATE `cloud`.`guest_os` + SET `category_id` = to_category_id + WHERE `category_id` = (SELECT `id` FROM `cloud`.`guest_os_category` WHERE `name` = from_category_name) +; UPDATE `cloud`.`guest_os_category` SET `removed`=now() WHERE `name` = from_category_name +; END; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-307to410.sql b/engine/schema/src/main/resources/META-INF/db/schema-307to410.sql index 55d78b594377..3b0cfa8e6ce3 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-307to410.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-307to410.sql @@ -3,7 +3,7 @@ -- 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 compliances +-- "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 diff --git a/engine/schema/src/main/resources/META-INF/db/schema-410to420.sql b/engine/schema/src/main/resources/META-INF/db/schema-410to420.sql index 35f73b35d3ca..d62a9bb93034 100644 --- a/engine/schema/src/main/resources/META-INF/db/schema-410to420.sql +++ b/engine/schema/src/main/resources/META-INF/db/schema-410to420.sql @@ -2305,7 +2305,7 @@ CREATE TABLE `cloud_usage`.`usage_vmsnapshot` ( ) ENGINE=InnoDB CHARSET=utf8; INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'healthcheck.update.interval', '600', 'Time Interval to fetch the LB health check states (in sec)'); -INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Snapshots', 'DEFAULT', 'SnapshotManager', 'kvm.snapshot.enabled', 'false', 'whether snapshot is enabled for KVM hosts'); +INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Snapshots', 'DEFAULT', 'SnapshotManager', 'kvm.snapshot.enabled', 'true', 'whether snapshot is enabled for KVM hosts'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Advanced', 'DEFAULT', 'management-server', 'eip.use.multiple.netscalers', 'false', 'Should be set to true, if there will be multiple NetScaler devices providing EIP service in a zone'); INSERT IGNORE INTO `cloud`.`configuration` VALUES ('Snapshots', 'DEFAULT', 'SnapshotManager', 'snapshot.backup.rightafter', 'true', 'backup snapshot right after snapshot is taken'); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42040to42100-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-42040to42100-cleanup.sql new file mode 100644 index 000000000000..b63e918b389b --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42040to42100-cleanup.sql @@ -0,0 +1,20 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +--; +-- Schema upgrade cleanup from 4.20.4.0 to 4.21.0.0 +--; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42040to42100.sql b/engine/schema/src/main/resources/META-INF/db/schema-42040to42100.sql new file mode 100644 index 000000000000..bf7f3aeaf0f8 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42040to42100.sql @@ -0,0 +1,761 @@ +-- 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. + +--; +-- Schema upgrade from 4.20.4.0 to 4.21.0.0 +--; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'max_backups', 'INT(8) UNSIGNED NOT NULL DEFAULT 0 COMMENT ''Maximum number of backups to be retained'''); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'backup_schedule_id', 'BIGINT(20) UNSIGNED'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'quiescevm', 'tinyint(1) default NULL COMMENT "Quiesce VM before taking backup"'); + +-- Update default value for the config 'vm.network.nic.max.secondary.ipaddresses' (and value to default value if value is null) +UPDATE `cloud`.`configuration` SET default_value = '10' WHERE name = 'vm.network.nic.max.secondary.ipaddresses'; +UPDATE `cloud`.`configuration` SET value = '10' WHERE name = 'vm.network.nic.max.secondary.ipaddresses' AND value IS NULL; + +-- Add console_endpoint_creator_address column to cloud.console_session table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'console_endpoint_creator_address', 'VARCHAR(45)'); + +-- Add client_address column to cloud.console_session table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'client_address', 'VARCHAR(45)'); + +-- Allow default roles to use quotaCreditsList +INSERT INTO `cloud`.`role_permissions` (uuid, role_id, rule, permission, sort_order) +SELECT uuid(), role_id, 'quotaCreditsList', permission, sort_order +FROM `cloud`.`role_permissions` rp +WHERE rp.rule = 'quotaStatement' + AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaCreditsList'); + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'last_mgmt_server_id', 'bigint unsigned DEFAULT NULL COMMENT "last management server this host is connected to" AFTER `mgmt_server_id`'); + +----------------------------------------------------------- +-- CKS Enhancements: +----------------------------------------------------------- +-- Add for_cks column to the vm_template table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_template','for_cks', 'int(1) unsigned DEFAULT "0" COMMENT "if true, the template can be used for CKS cluster deployment"'); + +-- Add support for different node types service offerings on CKS clusters +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','control_node_service_offering_id', 'bigint unsigned COMMENT "service offering ID for Control Node(s)"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','worker_node_service_offering_id', 'bigint unsigned COMMENT "service offering ID for Worker Node(s)"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','etcd_node_service_offering_id', 'bigint unsigned COMMENT "service offering ID for etcd Nodes"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','etcd_node_count', 'bigint unsigned COMMENT "number of etcd nodes to be deployed for the Kubernetes cluster"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','control_node_template_id', 'bigint unsigned COMMENT "template id to be used for Control Node(s)"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','worker_node_template_id', 'bigint unsigned COMMENT "template id to be used for Worker Node(s)"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','etcd_node_template_id', 'bigint unsigned COMMENT "template id to be used for etcd Nodes"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','cni_config_id', 'bigint unsigned COMMENT "user data id representing the associated cni configuration"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster','cni_config_details', 'varchar(4096) DEFAULT NULL COMMENT "user data details representing the values required for the cni configuration associated"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster_vm_map','etcd_node', 'tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT "indicates if the VM is an etcd node"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster_vm_map','external_node', 'tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT "indicates if the node was imported into the Kubernetes cluster"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster_vm_map','manual_upgrade', 'tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT "indicates if the node is marked for manual upgrade and excluded from the Kubernetes cluster upgrade operation"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster_vm_map','kubernetes_node_version', 'varchar(40) COMMENT "version of k8s the cluster node is on"'); + +ALTER TABLE `cloud`.`kubernetes_cluster` ADD CONSTRAINT `fk_cluster__control_node_service_offering_id` FOREIGN KEY `fk_cluster__control_node_service_offering_id`(`control_node_service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`kubernetes_cluster` ADD CONSTRAINT `fk_cluster__worker_node_service_offering_id` FOREIGN KEY `fk_cluster__worker_node_service_offering_id`(`worker_node_service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`kubernetes_cluster` ADD CONSTRAINT `fk_cluster__etcd_node_service_offering_id` FOREIGN KEY `fk_cluster__etcd_node_service_offering_id`(`etcd_node_service_offering_id`) REFERENCES `service_offering`(`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`kubernetes_cluster` ADD CONSTRAINT `fk_cluster__control_node_template_id` FOREIGN KEY `fk_cluster__control_node_template_id`(`control_node_template_id`) REFERENCES `vm_template`(`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`kubernetes_cluster` ADD CONSTRAINT `fk_cluster__worker_node_template_id` FOREIGN KEY `fk_cluster__worker_node_template_id`(`worker_node_template_id`) REFERENCES `vm_template`(`id`) ON DELETE CASCADE; +ALTER TABLE `cloud`.`kubernetes_cluster` ADD CONSTRAINT `fk_cluster__etcd_node_template_id` FOREIGN KEY `fk_cluster__etcd_node_template_id`(`etcd_node_template_id`) REFERENCES `vm_template`(`id`) ON DELETE CASCADE; + +-- Add for_cks column to the user_data table to represent CNI Configuration stored as userdata +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user_data','for_cks', 'int(1) unsigned DEFAULT "0" COMMENT "if true, the user data represent CNI configuration meant for CKS use only"'); + +-- Add use VR IP as resolver option on VPC +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc','use_router_ip_resolver', 'tinyint(1) DEFAULT 0 COMMENT "use router ip as resolver instead of dns options"'); +----------------------------------------------------------- +-- END - CKS Enhancements +----------------------------------------------------------- + +-- Add table for reconcile commands +CREATE TABLE IF NOT EXISTS `cloud`.`reconcile_commands` ( + `id` bigint unsigned NOT NULL UNIQUE AUTO_INCREMENT, + `management_server_id` bigint unsigned NOT NULL COMMENT 'node id of the management server', + `host_id` bigint unsigned NOT NULL COMMENT 'id of the host', + `request_sequence` bigint unsigned NOT NULL COMMENT 'sequence of the request', + `resource_id` bigint unsigned DEFAULT NULL COMMENT 'id of the resource', + `resource_type` varchar(255) COMMENT 'type if the resource', + `state_by_management` varchar(255) COMMENT 'state of the command updated by management server', + `state_by_agent` varchar(255) COMMENT 'state of the command updated by cloudstack agent', + `command_name` varchar(255) COMMENT 'name of the command', + `command_info` MEDIUMTEXT COMMENT 'info of the command', + `answer_name` varchar(255) COMMENT 'name of the answer', + `answer_info` MEDIUMTEXT COMMENT 'info of the answer', + `created` datetime COMMENT 'date the reconcile command was created', + `removed` datetime COMMENT 'date the reconcile command was removed', + `updated` datetime COMMENT 'date the reconcile command was updated', + `retry_count` bigint unsigned DEFAULT 0 COMMENT 'The retry count of reconciliation', + PRIMARY KEY(`id`), + INDEX `i_reconcile_command__host_id`(`host_id`), + CONSTRAINT `fk_reconcile_command__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +--- KVM Incremental Snapshots + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.snapshot_store_ref', 'kvm_checkpoint_path', 'varchar(255)'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.snapshot_store_ref', 'end_of_chain', 'int(1) unsigned'); + +-- Create table storage_pool_and_access_group_map +CREATE TABLE IF NOT EXISTS `cloud`.`storage_pool_and_access_group_map` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `pool_id` bigint(20) unsigned NOT NULL COMMENT "pool id", + `storage_access_group` varchar(255) NOT NULL, + PRIMARY KEY (`id`), + KEY `fk_storage_pool_and_access_group_map__pool_id` (`pool_id`), + CONSTRAINT `fk_storage_pool_and_access_group_map__pool_id` FOREIGN KEY (`pool_id`) REFERENCES `storage_pool` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the host"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.cluster', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the cluster"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.host_pod_ref', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the pod"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.data_center', 'storage_access_groups', 'varchar(255) DEFAULT NULL COMMENT "storage access groups for the hosts in the zone"'); + +-- Add featured, sort_key, created, removed columns for guest_os_category +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'featured', 'tinyint(1) NOT NULL DEFAULT 0 COMMENT "whether the category is featured or not" AFTER `uuid`'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'sort_key', 'int NOT NULL DEFAULT 0 COMMENT "sort key used for customising sort method" AFTER `featured`'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'created', 'datetime COMMENT "date on which the category was created" AFTER `sort_key`'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.guest_os_category', 'removed', 'datetime COMMENT "date removed if not null" AFTER `created`'); + +-- Begin: Changes for Guest OS category cleanup +-- Add new OS categories if not present +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`; +CREATE PROCEDURE `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`(IN os_name VARCHAR(255)) +BEGIN + IF NOT EXISTS ((SELECT 1 FROM `cloud`.`guest_os_category` WHERE name = os_name)) + THEN + INSERT INTO `cloud`.`guest_os_category` (name, uuid) + VALUES (os_name, UUID()) +; END IF +; END; + +CALL `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`('Fedora'); +CALL `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`('Rocky Linux'); +CALL `cloud`.`INSERT_CATEGORY_IF_NOT_EXIST`('AlmaLinux'); + +-- Move existing guest OS to new categories +DROP PROCEDURE IF EXISTS `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`; +CREATE PROCEDURE `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`(IN category_name VARCHAR(255), IN os_name VARCHAR(255)) +BEGIN + DECLARE category_id BIGINT +; SELECT `id` INTO category_id + FROM `cloud`.`guest_os_category` + WHERE `name` = category_name + LIMIT 1 +; IF category_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Category not found' +; END IF +; UPDATE `cloud`.`guest_os` + SET `category_id` = category_id + WHERE `display_name` LIKE CONCAT('%', os_name, '%') +; END; +CALL `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`('Rocky Linux', 'Rocky Linux'); +CALL `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`('AlmaLinux', 'AlmaLinux'); +CALL `cloud`.`UPDATE_CATEGORY_FOR_GUEST_OSES`('Fedora', 'Fedora'); + +-- Move existing guest OS whose category will be deleted to Other category +DROP PROCEDURE IF EXISTS `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`; +CREATE PROCEDURE `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`(IN to_category_name VARCHAR(255), IN from_category_name VARCHAR(255)) +BEGIN + DECLARE done INT DEFAULT 0 +; DECLARE to_category_id BIGINT +; SELECT id INTO to_category_id + FROM `cloud`.`guest_os_category` + WHERE `name` = to_category_name + LIMIT 1 +; IF to_category_id IS NULL THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'ToCategory not found' +; END IF +; UPDATE `cloud`.`guest_os` + SET `category_id` = to_category_id + WHERE `category_id` = (SELECT `id` FROM `cloud`.`guest_os_category` WHERE `name` = from_category_name) +; UPDATE `cloud`.`guest_os_category` SET `removed`=now() WHERE `name` = from_category_name +; END; +CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'Novel'); +CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'None'); +CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'Unix'); +CALL `cloud`.`UPDATE_NEW_AND_DELETE_OLD_CATEGORY_FOR_GUEST_OS`('Other', 'Mac'); + +-- Update featured for existing guest OS categories +UPDATE `cloud`.`guest_os_category` SET featured = 1; + +-- Update sort order for all guest OS categories +UPDATE `cloud`.`guest_os_category` +SET `sort_key` = CASE + WHEN `name` = 'Ubuntu' THEN 1 + WHEN `name` = 'Debian' THEN 2 + WHEN `name` = 'Fedora' THEN 3 + WHEN `name` = 'CentOS' THEN 4 + WHEN `name` = 'Rocky Linux' THEN 5 + WHEN `name` = 'AlmaLinux' THEN 6 + WHEN `name` = 'Oracle' THEN 7 + WHEN `name` = 'RedHat' THEN 8 + WHEN `name` = 'SUSE' THEN 9 + WHEN `name` = 'Windows' THEN 10 + WHEN `name` = 'Other' THEN 11 + ELSE `sort_key` +END; +-- End: Changes for Guest OS category cleanup + +-- Update description for configuration: host.capacityType.to.order.clusters +UPDATE `cloud`.`configuration` SET + `description` = 'The host capacity type (CPU, RAM or COMBINED) is used by deployment planner to order clusters during VM resource allocation' +WHERE `name` = 'host.capacityType.to.order.clusters' + AND `description` = 'The host capacity type (CPU or RAM) is used by deployment planner to order clusters during VM resource allocation'; + +-- Whitelabel GUI +CREATE TABLE IF NOT EXISTS `cloud`.`gui_themes` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `uuid` varchar(255) UNIQUE, + `name` varchar(2048) NOT NULL COMMENT 'A name to identify the theme.', + `description` varchar(4096) DEFAULT NULL COMMENT 'A description for the theme.', + `css` text DEFAULT NULL COMMENT 'The CSS to be retrieved and imported into the GUI when matching the theme access configurations.', + `json_configuration` text DEFAULT NULL COMMENT 'The JSON with the configurations to be retrieved and imported into the GUI when matching the theme access configurations.', + `recursive_domains` tinyint(1) DEFAULT 0 COMMENT 'Defines whether the subdomains of the informed domains are considered. Default value is false.', + `is_public` tinyint(1) default 1 COMMENT 'Defines whether a theme can be retrieved by anyone when only the `internet_domains_names` is informed. If the `domain_uuids` or `account_uuids` is informed, it is considered as `false`.', + `created` datetime NOT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +); + +CREATE TABLE IF NOT EXISTS `cloud`.`gui_themes_details` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `gui_theme_id` bigint(20) unsigned NOT NULL COMMENT 'Foreign key referencing the GUI theme on `gui_themes` table.', + `type` varchar(100) NOT NULL COMMENT 'The type of GUI theme details. Valid options are: `account`, `domain` and `commonName`', + `value` text NOT NULL COMMENT 'The value of the `type` details. Can be an UUID (account or domain) or internet common name.', + PRIMARY KEY (`id`), + CONSTRAINT `fk_gui_themes_details__gui_theme_id` FOREIGN KEY (`gui_theme_id`) REFERENCES `gui_themes`(`id`) +); + +-- Create the GPU card table to hold the GPU card information +CREATE TABLE IF NOT EXISTS `cloud`.`gpu_card` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `device_id` varchar(4) NOT NULL COMMENT 'device id of the GPU card', + `device_name` varchar(255) NOT NULL COMMENT 'device name of the GPU card', + `name` varchar(255) NOT NULL COMMENT 'name of the GPU card', + `vendor_name` varchar(255) NOT NULL COMMENT 'vendor name of the GPU card', + `vendor_id` varchar(4) NOT NULL COMMENT 'vendor id of the GPU card', + `created` datetime NOT NULL COMMENT 'date created', + PRIMARY KEY (`id`), + UNIQUE KEY (`vendor_id`, `device_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GPU cards supported by CloudStack'; + +-- Create the vGPU profile table to hold the vGPU profile information. +CREATE TABLE IF NOT EXISTS `cloud`.`vgpu_profile` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `name` varchar(255) NOT NULL COMMENT 'name of the vGPU profile', + `description` varchar(255) DEFAULT NULL COMMENT 'description of the vGPU profile', + `card_id` bigint unsigned NOT NULL COMMENT 'id of the GPU card', + `video_ram` bigint unsigned DEFAULT NULL COMMENT 'video RAM of the vGPU profile', + `max_heads` bigint unsigned DEFAULT NULL COMMENT 'maximum number of heads of the vGPU profile', + `max_resolution_x` bigint unsigned DEFAULT NULL COMMENT 'maximum resolution x of the vGPU profile', + `max_resolution_y` bigint unsigned DEFAULT NULL COMMENT 'maximum resolution y of the vGPU profile', + `max_vgpu_per_pgpu` bigint unsigned DEFAULT NULL COMMENT 'Maximum number of vGPUs per physical GPU', + `created` datetime NOT NULL COMMENT 'date created', + PRIMARY KEY (`id`), + UNIQUE KEY (`name`, `card_id`), + CONSTRAINT `fk_vgpu_profile_card_id` FOREIGN KEY (`card_id`) REFERENCES `gpu_card`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='vGPU profiles supported by CloudStack'; + +-- Create the GPU device table to hold the GPU device information on different hosts +CREATE TABLE IF NOT EXISTS `cloud`.`gpu_device` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `uuid` varchar(40) NOT NULL UNIQUE, + `card_id` bigint unsigned NOT NULL COMMENT 'id of the GPU card', + `vgpu_profile_id` bigint unsigned DEFAULT NULL COMMENT 'id of the vGPU profile.', + `bus_address` varchar(255) NOT NULL COMMENT 'PCI bus address of the GPU device', + `type` varchar(32) NOT NULL COMMENT 'type of the GPU device. PCI or MDEV', + `host_id` bigint unsigned NOT NULL COMMENT 'id of the host where GPU is installed', + `vm_id` bigint unsigned DEFAULT NULL COMMENT 'id of the VM using this GPU device', + `numa_node` varchar(255) DEFAULT NULL COMMENT 'NUMA node of the GPU device', + `pci_root` varchar(255) DEFAULT NULL COMMENT 'PCI root of the GPU device', + `parent_gpu_device_id` bigint unsigned DEFAULT NULL COMMENT 'id of the parent GPU device. null if it is a physical GPU device and for vGPUs points to the actual GPU', + `state` varchar(32) NOT NULL COMMENT 'state of the GPU device', + `managed_state` varchar(32) NOT NULL COMMENT 'resource state of the GPU device', + PRIMARY KEY (`id`), + UNIQUE KEY (`bus_address`, `host_id`), + CONSTRAINT `fk_gpu_devices__card_id` FOREIGN KEY (`card_id`) REFERENCES `gpu_card` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_gpu_devices__host_id` FOREIGN KEY (`host_id`) REFERENCES `host` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_gpu_devices__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `vm_instance` (`id`) ON DELETE SET NULL, + CONSTRAINT `fk_gpu_devices__parent_gpu_device_id` FOREIGN KEY (`parent_gpu_device_id`) REFERENCES `gpu_device` (`id`) ON DELETE SET NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='GPU devices installed on hosts'; + +-- Add references to GPU tables +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.service_offering', 'vgpu_profile_id', 'bigint unsigned DEFAULT NULL COMMENT "vgpu profile ID"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.service_offering', 'gpu_count', 'int unsigned DEFAULT NULL COMMENT "number of GPUs"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.service_offering', 'gpu_display', 'boolean DEFAULT false COMMENT "enable GPU display"'); +CALL `cloud`.`IDEMPOTENT_DROP_FOREIGN_KEY`('cloud.service_offering','fk_service_offering__vgpu_profile_id'); +CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.service_offering', 'fk_service_offering__vgpu_profile_id', '(vgpu_profile_id)', '`vgpu_profile`(`id`)'); + +-- Netris Plugin +CREATE TABLE IF NOT EXISTS `cloud`.`netris_providers` ( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40), + `zone_id` bigint unsigned NOT NULL COMMENT 'Zone ID', + `host_id` bigint unsigned NOT NULL COMMENT 'Host ID', + `name` varchar(40), + `url` varchar(255) NOT NULL, + `username` varchar(255) NOT NULL, + `password` varchar(255) NOT NULL, + `site_name` varchar(255) NOT NULL, + `tenant_name` varchar(255) NOT NULL, + `netris_tag` varchar(255) NOT NULL, + `created` datetime NOT NULL COMMENT 'created date', + `removed` datetime COMMENT 'removed date if not null', + PRIMARY KEY (`id`), + CONSTRAINT `fk_netris_providers__zone_id` FOREIGN KEY `fk_netris_providers__zone_id` (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE, + INDEX `i_netris_providers__zone_id`(`zone_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Drop the Tungsten and NSX columns from the network offerings (replaced by checking the provider on the ntwk_offering_service_map table) +CALL `cloud`.`IDEMPOTENT_DROP_COLUMN`('cloud.network_offerings', 'for_tungsten'); +CALL `cloud`.`IDEMPOTENT_DROP_COLUMN`('cloud.network_offerings', 'for_nsx'); + +-- Drop the Tungsten and NSX columns from the VPC offerings (replaced by checking the provider on the vpc_offering_service_map table) +CALL `cloud`.`IDEMPOTENT_DROP_COLUMN`('cloud.vpc_offerings', 'for_nsx'); + +-- Add next_hop to the static_routes table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.static_routes', 'next_hop', 'varchar(50) COMMENT "next hop of the static route" AFTER `vpc_gateway_id`'); + +-- Add `for_router` to `user_ip_address` table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.user_ip_address', 'for_router', 'tinyint(1) DEFAULT 0 COMMENT "True if the ip address is used by Domain Router to expose services"'); + +-- Add Netris Autoscaling rules +INSERT IGNORE INTO `cloud`.`counter` (uuid, provider, source, name, value, created) VALUES (UUID(), 'Netris', 'cpu', 'VM CPU - average percentage', 'vm.cpu.average.percentage', NOW()); +INSERT IGNORE INTO `cloud`.`counter` (uuid, provider, source, name, value, created) VALUES (UUID(), 'Netris', 'memory', 'VM Memory - average percentage', 'vm.memory.average.percentage', NOW()); + +-- Rename user_vm_details to vm_instance_details +ALTER TABLE `cloud`.`user_vm_details` RENAME TO `cloud`.`vm_instance_details`; +ALTER TABLE `cloud`.`vm_instance_details` DROP FOREIGN KEY `fk_user_vm_details__vm_id`; +ALTER TABLE `cloud`.`vm_instance_details` ADD CONSTRAINT `fk_vm_instance_details__vm_id` FOREIGN KEY (vm_id) REFERENCES vm_instance(id) ON DELETE CASCADE; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'uuid', 'VARCHAR(40) NOT NULL'); +UPDATE `cloud`.`backup_schedule` SET uuid = UUID(); + +-- Extension framework +UPDATE `cloud`.`configuration` SET value = CONCAT(value, ',External') WHERE name = 'hypervisor.list'; + +CREATE TABLE IF NOT EXISTS `cloud`.`extension` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(40) NOT NULL UNIQUE, + `name` varchar(255) NOT NULL, + `description` varchar(4096), + `type` varchar(255) NOT NULL COMMENT 'Type of the extension: Orchestrator, etc', + `relative_path` varchar(2048) NOT NULL COMMENT 'Path for the extension relative to the root extensions directory', + `path_ready` tinyint(1) DEFAULT '0' COMMENT 'True if the extension path is in ready state across management servers', + `is_user_defined` tinyint(1) DEFAULT '0' COMMENT 'True if the extension is added by admin', + `state` char(32) NOT NULL COMMENT 'State of the extension - Enabled or Disabled', + `created` datetime NOT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`extension_details` ( + `id` bigint unsigned UNIQUE NOT NULL AUTO_INCREMENT COMMENT 'id', + `extension_id` bigint unsigned NOT NULL COMMENT 'extension to which the detail is related to', + `name` varchar(255) NOT NULL COMMENT 'name of the detail', + `value` varchar(255) NOT NULL COMMENT 'value of the detail', + `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_extension_details__extension_id` FOREIGN KEY (`extension_id`) + REFERENCES `extension` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`extension_resource_map` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `extension_id` bigint(20) unsigned NOT NULL, + `resource_id` bigint(20) unsigned NOT NULL, + `resource_type` char(255) NOT NULL, + `created` datetime NOT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_extension_resource_map__extension_id` FOREIGN KEY (`extension_id`) + REFERENCES `cloud`.`extension`(`id`) ON DELETE CASCADE, + INDEX `idx_extension_resource` (`resource_id`, `resource_type`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`extension_resource_map_details` ( + `id` bigint unsigned UNIQUE NOT NULL AUTO_INCREMENT COMMENT 'id', + `extension_resource_map_id` bigint unsigned NOT NULL COMMENT 'mapping to which the detail is related', + `name` varchar(255) NOT NULL COMMENT 'name of the detail', + `value` varchar(255) NOT NULL COMMENT 'value of the detail', + `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_extension_resource_map_details__map_id` FOREIGN KEY (`extension_resource_map_id`) + REFERENCES `extension_resource_map` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`extension_custom_action` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `uuid` varchar(255) NOT NULL UNIQUE, + `name` varchar(255) NOT NULL, + `description` varchar(4096), + `extension_id` bigint(20) unsigned NOT NULL, + `resource_type` varchar(255), + `allowed_role_types` int unsigned NOT NULL DEFAULT '1', + `success_message` varchar(4096), + `error_message` varchar(4096), + `enabled` boolean DEFAULT true, + `timeout` int unsigned NOT NULL DEFAULT '5' COMMENT 'The timeout in seconds to wait for the action to complete before failing', + `created` datetime NOT NULL, + `removed` datetime DEFAULT NULL, + PRIMARY KEY (`id`), + CONSTRAINT `fk_extension_custom_action__extension_id` FOREIGN KEY (`extension_id`) + REFERENCES `cloud`.`extension`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CREATE TABLE IF NOT EXISTS `cloud`.`extension_custom_action_details` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `extension_custom_action_id` bigint(20) unsigned NOT NULL, + `name` varchar(255) NOT NULL, + `value` TEXT NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT 1, + PRIMARY KEY (`id`), + CONSTRAINT `fk_custom_action_details__action_id` FOREIGN KEY (`extension_custom_action_id`) + REFERENCES `cloud`.`extension_custom_action`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vm_template', 'extension_id', 'bigint unsigned DEFAULT NULL COMMENT "id of the extension"'); + +-- Add built-in Extensions and Custom Actions + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`( + IN ext_name VARCHAR(255), + IN ext_desc VARCHAR(255), + IN ext_path VARCHAR(255) +) +BEGIN + IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension` WHERE `name` = ext_name + ) THEN + INSERT INTO `cloud`.`extension` ( + `uuid`, `name`, `description`, `type`, + `relative_path`, `path_ready`, + `is_user_defined`, `state`, `created`, `removed` + ) + VALUES ( + UUID(), ext_name, ext_desc, 'Orchestrator', + ext_path, 1, 0, 'Enabled', NOW(), NULL + ) +; END IF +;END; + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`( + IN ext_name VARCHAR(255), + IN detail_key VARCHAR(255), + IN detail_value TEXT, + IN display TINYINT(1) +) +BEGIN + DECLARE ext_id BIGINT +; SELECT `id` INTO ext_id FROM `cloud`.`extension` WHERE `name` = ext_name LIMIT 1 +; IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension_details` + WHERE `extension_id` = ext_id AND `name` = detail_key + ) THEN + INSERT INTO `cloud`.`extension_details` ( + `extension_id`, `name`, `value`, `display` + ) + VALUES ( + ext_id, detail_key, detail_value, display + ) +; END IF +;END; + +CALL `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`('Proxmox', 'Sample extension for Proxmox written in bash', 'Proxmox/proxmox.sh'); +CALL `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`('Proxmox', 'orchestratorrequirespreparevm', 'true', 0); + +CALL `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`('HyperV', 'Sample extension for HyperV written in python', 'HyperV/hyperv.py'); + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`( + IN ext_name VARCHAR(255), + IN action_name VARCHAR(255), + IN action_desc VARCHAR(4096), + IN resource_type VARCHAR(255), + IN allowed_roles INT UNSIGNED, + IN success_msg VARCHAR(4096), + IN error_msg VARCHAR(4096), + IN timeout_seconds INT UNSIGNED +) +BEGIN + DECLARE ext_id BIGINT +; SELECT `id` INTO ext_id FROM `cloud`.`extension` WHERE `name` = ext_name LIMIT 1 +; IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension_custom_action` WHERE `name` = action_name AND `extension_id` = ext_id + ) THEN + INSERT INTO `cloud`.`extension_custom_action` ( + `uuid`, `name`, `description`, `extension_id`, `resource_type`, + `allowed_role_types`, `success_message`, `error_message`, + `enabled`, `timeout`, `created`, `removed` + ) + VALUES ( + UUID(), action_name, action_desc, ext_id, resource_type, + allowed_roles, success_msg, error_msg, + 1, timeout_seconds, NOW(), NULL + ) +; END IF +;END; + +DROP PROCEDURE IF EXISTS `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`; +CREATE PROCEDURE `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS` ( + IN ext_name VARCHAR(255), + IN action_name VARCHAR(255), + IN param_json TEXT +) +BEGIN + DECLARE action_id BIGINT UNSIGNED +; SELECT `eca`.`id` INTO action_id FROM `cloud`.`extension_custom_action` `eca` + JOIN `cloud`.`extension` `e` ON `e`.`id` = `eca`.`extension_id` + WHERE `eca`.`name` = action_name AND `e`.`name` = ext_name LIMIT 1 +; IF NOT EXISTS ( + SELECT 1 FROM `cloud`.`extension_custom_action_details` + WHERE `extension_custom_action_id` = action_id + AND `name` = 'parameters' + ) THEN + INSERT INTO `cloud`.`extension_custom_action_details` ( + `extension_custom_action_id`, + `name`, + `value`, + `display` + ) VALUES ( + action_id, + 'parameters', + param_json, + 0 + ) +; END IF +;END; + +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'ListSnapshots', 'List Instance snapshots', 'VirtualMachine', 15, 'Snapshots fetched for {{resourceName}} in {{extensionName}}', 'List Snapshots failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'CreateSnapshot', 'Create an Instance snapshot', 'VirtualMachine', 15, 'Snapshot created for {{resourceName}} in {{extensionName}}', 'Snapshot creation failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'RestoreSnapshot', 'Restore Instance to the specific snapshot', 'VirtualMachine', 15, 'Successfully restored snapshot for {{resourceName}} in {{extensionName}}', 'Restore snapshot failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('Proxmox', 'DeleteSnapshot', 'Delete the specified snapshot', 'VirtualMachine', 15, 'Successfully deleted snapshot for {{resourceName}} in {{extensionName}}', 'Delete snapshot failed for {{resourceName}}', 60); + +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'Proxmox', + 'ListSnapshots', + '[]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'Proxmox', + 'CreateSnapshot', + '[ + { + "name": "snap_name", + "type": "STRING", + "validationformat": "NONE", + "required": true + }, + { + "name": "snap_description", + "type": "STRING", + "validationformat": "NONE", + "required": false + }, + { + "name": "snap_save_memory", + "type": "BOOLEAN", + "validationformat": "NONE", + "required": false + } + ]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'Proxmox', + 'RestoreSnapshot', + '[ + { + "name": "snap_name", + "type": "STRING", + "validationformat": "NONE", + "required": true + } + ]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'Proxmox', + 'DeleteSnapshot', + '[ + { + "name": "snap_name", + "type": "STRING", + "validationformat": "NONE", + "required": true + } + ]' +); + +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('HyperV', 'ListSnapshots', 'List checkpoints/snapshots for the Instance', 'VirtualMachine', 15, 'Snapshots fetched for {{resourceName}} in {{extensionName}}', 'List Snapshots failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('HyperV', 'CreateSnapshot', 'Create a checkpoint/snapshot for the Instance', 'VirtualMachine', 15, 'Snapshot created for {{resourceName}} in {{extensionName}}', 'Snapshot creation failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('HyperV', 'RestoreSnapshot', 'Restore Instance to the specified snapshot', 'VirtualMachine', 15, 'Successfully restored snapshot for {{resourceName}} in {{extensionName}}', 'Restore snapshot failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('HyperV', 'DeleteSnapshot', 'Delete the specified snapshot', 'VirtualMachine', 15, 'Successfully deleted snapshot for {{resourceName}} in {{extensionName}}', 'Delete snapshot failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('HyperV', 'Suspend', 'Suspend the Instance by freezing its current state in RAM', 'VirtualMachine', 15, 'Successfully suspended {{resourceName}} in {{extensionName}}', 'Suspend failed for {{resourceName}}', 60); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_IF_NOT_EXISTS`('HyperV', 'Resume', 'Resumes a suspended Instance, restoring CPU execution from memory.', 'VirtualMachine', 15, 'Successfully resumed {{resourceName}} in {{extensionName}}', 'Resume failed for {{resourceName}}', 60); + +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'HyperV', + 'ListSnapshots', + '[]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'HyperV', + 'CreateSnapshot', + '[ + { + "name": "snapshot_name", + "type": "STRING", + "validationformat": "NONE", + "required": true + } + ]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'HyperV', + 'RestoreSnapshot', + '[ + { + "name": "snapshot_name", + "type": "STRING", + "validationformat": "NONE", + "required": true + } + ]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'HyperV', + 'DeleteSnapshot', + '[ + { + "name": "snapshot_name", + "type": "STRING", + "validationformat": "NONE", + "required": true + } + ]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'HyperV', + 'Suspend', + '[]' +); +CALL `cloud`.`INSERT_EXTENSION_CUSTOM_ACTION_DETAILS_IF_NOT_EXISTS`( + 'HyperV', + 'Resume', + '[]' +); + +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `cidr` varchar(255) DEFAULT NULL COMMENT 'CloudStack managed vms get IP address from cidr.In general this cidr also serves as the network CIDR. But in case IP reservation feature is being used by a Guest network, networkcidr is the Effective network CIDR for that network'; +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `gateway` varchar(255) DEFAULT NULL COMMENT 'gateway(s) for this network configuration'; +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `ip6_cidr` varchar(1024) DEFAULT NULL COMMENT 'IPv6 cidr(s) for this network'; +ALTER TABLE `cloud`.`networks` MODIFY COLUMN `ip6_gateway` varchar(1024) DEFAULT NULL COMMENT 'IPv6 gateway(s) for this network'; + +-- Add columns name, description and backup_interval_type to backup table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'name', 'VARCHAR(255) NULL COMMENT "name of the backup"'); +UPDATE `cloud`.`backups` backup INNER JOIN `cloud`.`vm_instance` vm ON backup.vm_id = vm.id SET backup.name = vm.name; +ALTER TABLE `cloud`.`backups` MODIFY COLUMN `name` VARCHAR(255) NOT NULL; +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'description', 'VARCHAR(1024) COMMENT "description for the backup"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'backup_interval_type', 'int(5) COMMENT "type of backup, e.g. manual, recurring - hourly, daily, weekly or monthly"'); + +-- Create backup details table +CREATE TABLE IF NOT EXISTS `cloud`.`backup_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `backup_id` bigint unsigned NOT NULL COMMENT 'backup id', + `name` varchar(255) NOT NULL, + `value` TEXT NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_backup_details__backup_id` FOREIGN KEY `fk_backup_details__backup_id`(`backup_id`) REFERENCES `backups`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Add diskOfferingId, deviceId, minIops and maxIops to backed_volumes in backups table +UPDATE `cloud`.`backups` b +INNER JOIN `cloud`.`vm_instance` vm ON b.vm_id = vm.id +SET b.backed_volumes = ( + SELECT COALESCE( + CAST( + JSON_ARRAYAGG( + JSON_OBJECT( + 'uuid', v.uuid, + 'type', v.volume_type, + 'size', v.size, + 'path', v.path, + 'deviceId', v.device_id, + 'diskOfferingId', doff.uuid, + 'minIops', v.min_iops, + 'maxIops', v.max_iops + ) + ) AS CHAR + ), + '[]' + ) + FROM `cloud`.`volumes` v + LEFT JOIN `cloud`.`disk_offering` doff ON v.disk_offering_id = doff.id + WHERE v.instance_id = vm.id +); + +-- Add diskOfferingId, deviceId, minIops and maxIops to backup_volumes in vm_instance table +UPDATE `cloud`.`vm_instance` vm +SET vm.backup_volumes = ( + SELECT COALESCE( + CAST( + JSON_ARRAYAGG( + JSON_OBJECT( + 'uuid', v.uuid, + 'type', v.volume_type, + 'size', v.size, + 'path', v.path, + 'deviceId', v.device_id, + 'diskOfferingId', doff.uuid, + 'minIops', v.min_iops, + 'maxIops', v.max_iops + ) + ) AS CHAR + ), + '[]' + ) + FROM `cloud`.`volumes` v + LEFT JOIN `cloud`.`disk_offering` doff ON v.disk_offering_id = doff.id + WHERE v.instance_id = vm.id +) +WHERE vm.backup_offering_id IS NOT NULL; + +-- Add column allocated_size to object_store table. Rename column 'used_bytes' to 'used_size' +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.object_store', 'allocated_size', 'bigint unsigned COMMENT "allocated size in bytes"'); +ALTER TABLE `cloud`.`object_store` CHANGE COLUMN `used_bytes` `used_size` BIGINT UNSIGNED COMMENT 'used size in bytes'; +ALTER TABLE `cloud`.`object_store` MODIFY COLUMN `total_size` bigint unsigned COMMENT 'total size in bytes'; +UPDATE `cloud`.`object_store` +JOIN ( + SELECT object_store_id, SUM(quota) AS total_quota + FROM `cloud`.`bucket` + WHERE removed IS NULL + GROUP BY object_store_id +) buckets_quota_sum_view ON `object_store`.id = buckets_quota_sum_view.object_store_id +SET `object_store`.allocated_size = buckets_quota_sum_view.total_quota; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.console_session', 'domain_id', 'bigint(20) unsigned NOT NULL'); + +UPDATE `cloud`.`console_session` `cs` +SET `cs`.`domain_id` = ( + SELECT `acc`.`domain_id` + FROM `cloud`.`account` `acc` + WHERE `acc`.`id` = `cs`.`account_id` +); + +-- Re-apply VPC: update default network offering for vpc tier to conserve_mode=1 (#8309) +UPDATE `cloud`.`network_offerings` SET conserve_mode = 1 WHERE name = 'DefaultIsolatedNetworkOfferingForVpcNetworks'; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200-cleanup.sql new file mode 100644 index 000000000000..0546613dfa34 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200-cleanup.sql @@ -0,0 +1,20 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +--; +-- Schema upgrade cleanup from 4.21.0.0 to 4.22.0.0 +--; diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql new file mode 100644 index 000000000000..fbb2fd079f9c --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42100to42200.sql @@ -0,0 +1,93 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +--; +-- Schema upgrade from 4.21.0.0 to 4.22.0.0 +--; + +-- health check status as enum +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('router_health_check', 'check_result', 'check_result', 'varchar(16) NOT NULL COMMENT "check executions result: SUCCESS, FAILURE, WARNING, UNKNOWN"'); + +-- Increase length of scripts_version column to 128 due to md5sum to sha512sum change +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.domain_router', 'scripts_version', 'scripts_version', 'VARCHAR(128)'); + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.snapshot_policy','domain_id', 'BIGINT(20) DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.snapshot_policy','account_id', 'BIGINT(20) DEFAULT NULL'); + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule','domain_id', 'BIGINT(20) DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule','account_id', 'BIGINT(20) DEFAULT NULL'); + +-- Increase the cache_mode column size from cloud.disk_offering table +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.disk_offering', 'cache_mode', 'cache_mode', 'varchar(18) DEFAULT "none" COMMENT "The disk cache mode to use for disks created with this offering"'); + +-- Add uuid column to ldap_configuration table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.ldap_configuration', 'uuid', 'VARCHAR(40) NOT NULL'); + +-- Populate uuid for existing rows where uuid is NULL or empty +UPDATE `cloud`.`ldap_configuration` SET uuid = UUID() WHERE uuid IS NULL OR uuid = ''; + +-- Add the column cross_zone_instance_creation to cloud.backup_repository. if enabled it means that new Instance can be created on all Zones from Backups on this Repository. +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_repository', 'cross_zone_instance_creation', 'TINYINT(1) DEFAULT NULL COMMENT ''Backup Repository can be used for disaster recovery on another zone'''); + +-- Updated display to false for password/token detail of the storage pool details +UPDATE `cloud`.`storage_pool_details` SET display = 0 WHERE name LIKE '%password%'; +UPDATE `cloud`.`storage_pool_details` SET display = 0 WHERE name LIKE '%token%'; + +-- Add csi_enabled column to kubernetes_cluster table to indicate if the cluster is using csi or not +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.kubernetes_cluster', 'csi_enabled', 'TINYINT(1) unsigned NOT NULL DEFAULT 0 COMMENT "true if kubernetes cluster is using csi, false otherwise" '); + +-- VMware to KVM migration improvements +CREATE TABLE IF NOT EXISTS `cloud`.`import_vm_task`( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40), + `zone_id` bigint unsigned NOT NULL COMMENT 'Zone ID', + `account_id` bigint unsigned NOT NULL COMMENT 'Account ID', + `user_id` bigint unsigned NOT NULL COMMENT 'User ID', + `vm_id` bigint unsigned COMMENT 'VM ID', + `display_name` varchar(255) COMMENT 'Display VM Name', + `vcenter` varchar(255) COMMENT 'VCenter', + `datacenter` varchar(255) COMMENT 'VCenter Datacenter name', + `source_vm_name` varchar(255) COMMENT 'Source VM name on vCenter', + `convert_host_id` bigint unsigned COMMENT 'Convert Host ID', + `import_host_id` bigint unsigned COMMENT 'Import Host ID', + `step` varchar(20) COMMENT 'Importing VM Task Step', + `state` varchar(20) COMMENT 'Importing VM Task State', + `description` varchar(255) COMMENT 'Importing VM Task Description', + `duration` bigint unsigned COMMENT 'Duration in milliseconds for the completed tasks', + `created` datetime NOT NULL COMMENT 'date created', + `updated` datetime COMMENT 'date updated if not null', + `removed` datetime COMMENT 'date removed if not null', + PRIMARY KEY (`id`), + CONSTRAINT `fk_import_vm_task__zone_id` FOREIGN KEY `fk_import_vm_task__zone_id` (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_import_vm_task__account_id` FOREIGN KEY `fk_import_vm_task__account_id` (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_import_vm_task__user_id` FOREIGN KEY `fk_import_vm_task__user_id` (`user_id`) REFERENCES `user`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_import_vm_task__vm_id` FOREIGN KEY `fk_import_vm_task__vm_id` (`vm_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_import_vm_task__convert_host_id` FOREIGN KEY `fk_import_vm_task__convert_host_id` (`convert_host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_import_vm_task__import_host_id` FOREIGN KEY `fk_import_vm_task__import_host_id` (`import_host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE, + INDEX `i_import_vm_task__zone_id`(`zone_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +CALL `cloud`.`INSERT_EXTENSION_IF_NOT_EXISTS`('MaaS', 'Baremetal Extension for Canonical MaaS written in Python', 'MaaS/maas.py'); +CALL `cloud`.`INSERT_EXTENSION_DETAIL_IF_NOT_EXISTS`('MaaS', 'orchestratorrequirespreparevm', 'true', 0); + +CALL `cloud`.`IDEMPOTENT_DROP_UNIQUE_KEY`('counter', 'uc_counter__provider__source__value'); +CALL `cloud`.`IDEMPOTENT_ADD_UNIQUE_KEY`('cloud.counter', 'uc_counter__provider__source__value__removed', '(provider, source, value, removed)'); + +-- Change scope for configuration - 'use.https.to.upload from' from StoragePool to Zone +UPDATE `cloud`.`configuration` SET `scope` = 2 WHERE `name` = 'use.https.to.upload'; +-- Delete the configuration for 'use.https.to.upload' from StoragePool +DELETE FROM `cloud`.`storage_pool_details` WHERE `name` = 'use.https.to.upload'; 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 new file mode 100644 index 000000000000..2f104568c140 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210-cleanup.sql @@ -0,0 +1,26 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +--; +-- 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-42200to42210.sql b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql new file mode 100644 index 000000000000..baa20e9f0ad5 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42200to42210.sql @@ -0,0 +1,61 @@ +-- 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. + +--; +-- Schema upgrade from 4.22.0.0 to 4.22.1.0 +--; + +-- Add vm_id column to usage_event table for volume usage events +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.usage_event','vm_id', 'bigint UNSIGNED NULL COMMENT "VM ID associated with volume usage events"'); +CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.usage_event','vm_id', 'bigint UNSIGNED NULL COMMENT "VM ID associated with volume usage events"'); + +-- Add vm_id column to cloud_usage.usage_volume table +CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.usage_volume','vm_id', 'bigint UNSIGNED NULL COMMENT "VM ID associated with the volume usage"'); + +ALTER TABLE `cloud`.`template_store_ref` MODIFY COLUMN `download_url` varchar(2048); + +UPDATE `cloud`.`alert` SET type = 33 WHERE name = 'ALERT.VR.PUBLIC.IFACE.MTU'; +UPDATE `cloud`.`alert` SET type = 34 WHERE name = 'ALERT.VR.PRIVATE.IFACE.MTU'; + +-- Update configuration 'kvm.ssh.to.agent' description and is_dynamic fields +UPDATE `cloud`.`configuration` SET description = 'True if the management server will restart the agent service via SSH into the KVM hosts after or during maintenance operations', is_dynamic = 1 WHERE name = 'kvm.ssh.to.agent'; + +-- Sanitize legacy network-level addressing fields for Public networks +UPDATE `cloud`.`networks` +SET `broadcast_uri` = NULL, + `gateway` = NULL, + `cidr` = NULL, + `ip6_gateway` = NULL, + `ip6_cidr` = NULL +WHERE `traffic_type` = 'Public'; + +UPDATE `cloud`.`vm_template` SET guest_os_id = 99 WHERE name = 'kvm-default-vm-import-dummy-template'; + +-- Update existing vm_template records with NULL type to "USER" +UPDATE `cloud`.`vm_template` SET `type` = 'USER' WHERE `type` IS NULL; + +-- remove unused config item +DELETE FROM `cloud`.`configuration` WHERE name = 'consoleproxy.cmd.port'; + +-- Drops the unused "backup_interval_type" column of the "cloud.backups" table +ALTER TABLE `cloud`.`backups` DROP COLUMN `backup_interval_type`; + +-- Update `user.password.reset.mail.template` configuration value to match new logic +UPDATE `cloud`.`configuration` +SET value = CONCAT_WS('\n', 'Hello {{username}}!', 'You have requested to reset your password. Please click the following link to reset your password:', '{{{resetLink}}}', 'If you did not request a password reset, please ignore this email.', '', 'Regards,', 'The CloudStack Team') +WHERE name = 'user.password.reset.mail.template' + AND value IN (CONCAT_WS('\n', 'Hello {{username}}!', 'You have requested to reset your password. Please click the following link to reset your password:', 'http://{{{resetLink}}}', 'If you did not request a password reset, please ignore this email.', '', 'Regards,', 'The CloudStack Team'), CONCAT_WS('\n', 'Hello {{username}}!', 'You have requested to reset your password. Please click the following link to reset your password:', '{{{domainUrl}}}{{{resetLink}}}', 'If you did not request a password reset, please ignore this email.', '', 'Regards,', 'The CloudStack Team')); diff --git a/engine/schema/src/main/resources/META-INF/db/schema-42210to42300-cleanup.sql b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300-cleanup.sql new file mode 100644 index 000000000000..e2b066af7800 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300-cleanup.sql @@ -0,0 +1,20 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +--; +-- Schema upgrade cleanup from 4.22.1.0 to 4.23.0.0 +--; 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 new file mode 100644 index 000000000000..ab5ac7b2b875 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/schema-42210to42300.sql @@ -0,0 +1,648 @@ +-- 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. + +--; +-- Schema upgrade from 4.22.1.0 to 4.23.0.0 +--; + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider', 'domain_id', 'bigint unsigned DEFAULT NULL COMMENT "NULL for global provider, domain ID for domain-specific" AFTER `redirect_uri`'); +CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.oauth_provider', 'fk_oauth_provider__domain_id', '(`domain_id`)', '`domain`(`id`)'); +CALL `cloud`.`IDEMPOTENT_ADD_KEY`('i_oauth_provider__domain_id', 'cloud.oauth_provider', '(`domain_id`)'); + +CALL `cloud`.`IDEMPOTENT_ADD_UNIQUE_KEY`('cloud.oauth_provider', 'uk_oauth_provider__provider_domain', '(`provider`, `domain_id`)'); + +CREATE TABLE `cloud`.`backup_offering_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `backup_offering_id` bigint unsigned NOT NULL COMMENT 'Backup offering id', + `name` varchar(255) NOT NULL, + `value` varchar(1024) NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_offering_details__backup_offering_id` FOREIGN KEY `fk_offering_details__backup_offering_id`(`backup_offering_id`) REFERENCES `backup_offering`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Update value to random for the config 'vm.allocation.algorithm' or 'volume.allocation.algorithm' if configured as userconcentratedpod_random +-- Update value to firstfit for the config 'vm.allocation.algorithm' or 'volume.allocation.algorithm' if configured as userconcentratedpod_firstfit +UPDATE `cloud`.`configuration` SET value='random' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_random'; +UPDATE `cloud`.`configuration` SET value='firstfit' WHERE name IN ('vm.allocation.algorithm', 'volume.allocation.algorithm') AND value='userconcentratedpod_firstfit'; + +-- Add Windows Server 2025 guest OS and mappings +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '7.0', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '7.0.1.0', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '7.0.2.0', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '7.0.3.0', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0.0.1', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0.0.2', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0.0.3', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0.1', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0.2', 'windows2022srvNext_64Guest'); +CALL ADD_GUEST_OS_AND_HYPERVISOR_MAPPING (6, 'Windows Server 2025 (64-bit)', 'VMware', '8.0.3', 'windows2022srvNext_64Guest'); + +-- Create kubernetes_cluster_affinity_group_map table for CKS per-node-type affinity groups +CREATE TABLE IF NOT EXISTS `cloud`.`kubernetes_cluster_affinity_group_map` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT, + `cluster_id` bigint unsigned NOT NULL COMMENT 'kubernetes cluster id', + `node_type` varchar(32) NOT NULL COMMENT 'CONTROL, WORKER, or ETCD', + `affinity_group_id` bigint unsigned NOT NULL COMMENT 'affinity group id', + PRIMARY KEY (`id`), + CONSTRAINT `fk_kubernetes_cluster_ag_map__cluster_id` FOREIGN KEY (`cluster_id`) REFERENCES `kubernetes_cluster`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kubernetes_cluster_ag_map__ag_id` FOREIGN KEY (`affinity_group_id`) REFERENCES `affinity_group`(`id`) ON DELETE CASCADE, + INDEX `i_kubernetes_cluster_ag_map__cluster_id`(`cluster_id`), + INDEX `i_kubernetes_cluster_ag_map__ag_id`(`affinity_group_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Create webhook_filter table +DROP TABLE IF EXISTS `cloud`.`webhook_filter`; +CREATE TABLE IF NOT EXISTS `cloud`.`webhook_filter` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the webhook filter', + `uuid` varchar(255) COMMENT 'uuid of the webhook filter', + `webhook_id` bigint unsigned NOT NULL COMMENT 'id of the webhook', + `type` varchar(20) COMMENT 'type of the filter', + `mode` varchar(20) COMMENT 'mode of the filter', + `match_type` varchar(20) COMMENT 'match type of the filter', + `value` varchar(256) NOT NULL COMMENT 'value of the filter used for matching', + `created` datetime NOT NULL COMMENT 'date created', + PRIMARY KEY (`id`), + INDEX `i_webhook_filter__webhook_id`(`webhook_id`), + CONSTRAINT `fk_webhook_filter__webhook_id` FOREIGN KEY(`webhook_id`) REFERENCES `webhook`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- "api_keypair" table for API and secret keys +CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `uuid` varchar(40) UNIQUE NOT NULL, + `name` varchar(255) NOT NULL, + `domain_id` bigint(20) unsigned NOT NULL, + `account_id` bigint(20) unsigned NOT NULL, + `user_id` bigint(20) unsigned NOT NULL, + `start_date` datetime, + `end_date` datetime, + `description` varchar(1024), + `api_key` varchar(255) NOT NULL, + `secret_key` varchar(255) NOT NULL, + `created` datetime NOT NULL, + `removed` datetime, + PRIMARY KEY (`id`), + CONSTRAINT `fk_api_keypair__user_id` FOREIGN KEY(`user_id`) REFERENCES `cloud`.`user`(`id`), + CONSTRAINT `fk_api_keypair__account_id` FOREIGN KEY(`account_id`) REFERENCES `cloud`.`account`(`id`), + CONSTRAINT `fk_api_keypair__domain_id` FOREIGN KEY(`domain_id`) REFERENCES `cloud`.`domain`(`id`) +); + +-- "api_keypair_permissions" table for API key pairs permissions +CREATE TABLE IF NOT EXISTS `cloud`.`api_keypair_permissions` ( + `id` bigint(20) unsigned NOT NULL auto_increment, + `uuid` varchar(40) UNIQUE, + `sort_order` bigint(20) unsigned NOT NULL DEFAULT 0, + `rule` varchar(255) NOT NULL, + `api_keypair_id` bigint(20) unsigned NOT NULL, + `permission` varchar(255) NOT NULL, + `description` varchar(255), + PRIMARY KEY (`id`), + CONSTRAINT `fk_keypair_permissions__api_keypair_id` FOREIGN KEY(`api_keypair_id`) REFERENCES `cloud`.`api_keypair`(`id`) +); + +-- Populate "api_keypair" table with existing user API keys +INSERT INTO `cloud`.`api_keypair` (uuid, user_id, domain_id, account_id, api_key, secret_key, created, name) +SELECT UUID(), user.id, account.domain_id, account.id, user.api_key, user.secret_key, NOW(), 'Active key pair' +FROM `cloud`.`user` AS user +JOIN `cloud`.`account` AS account ON user.account_id = account.id +WHERE user.api_key IS NOT NULL AND user.secret_key IS NOT NULL; + +-- Drop API keys from user table +ALTER TABLE `cloud`.`user` DROP COLUMN api_key, DROP COLUMN secret_key; + +-- Grant access to the "deleteUserKeys" and "listUserKeyRules" APIs to the "User", "Domain Admin" and "Resource Admin" roles, similarly to the "registerUserKeys" API +CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'deleteUserKeys', 'ALLOW'); +CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'deleteUserKeys', 'ALLOW'); +CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'deleteUserKeys', 'ALLOW'); + +CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('User', 'listUserKeyRules', 'ALLOW'); +CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Domain Admin', 'listUserKeyRules', 'ALLOW'); +CALL `cloud`.`IDEMPOTENT_UPDATE_API_PERMISSION`('Resource Admin', 'listUserKeyRules', 'ALLOW'); + +-- Add conserve mode for VPC offerings +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.vpc_offerings','conserve_mode', 'tinyint(1) unsigned NULL DEFAULT 0 COMMENT ''True if the VPC offering is IP conserve mode enabled, allowing public IP services to be used across multiple VPC tiers'' '); + +-- KMS HSM Profiles (Generic table for PKCS#11, KMIP, etc.) +-- Scoped to account (user-provided) or global/zone (admin-provided) +CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profiles` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `uuid` VARCHAR(40) NOT NULL, + `name` VARCHAR(255) NOT NULL, + `protocol` VARCHAR(32) NOT NULL COMMENT 'Protocol for the HSM Profile', + + -- Scoping + `account_id` BIGINT UNSIGNED COMMENT 'null = admin-provided (available to all accounts)', + `domain_id` BIGINT UNSIGNED COMMENT 'null = zone/global scope', + `zone_id` BIGINT UNSIGNED COMMENT 'null = global scope', + + -- Metadata + `vendor_name` VARCHAR(64) COMMENT 'HSM vendor', + `enabled` BOOLEAN NOT NULL DEFAULT TRUE, + `is_public` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'Public profile. Available to all accounts', + `created` DATETIME NOT NULL, + `removed` DATETIME, + + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uuid` (`uuid`), + UNIQUE KEY `uk_account_name` (`account_id`, `name`, `removed`), + INDEX `idx_protocol_enabled` (`protocol`, `enabled`, `removed`), + INDEX `idx_scoping` (`account_id`, `domain_id`, `zone_id`, `removed`), + CONSTRAINT `fk_kms_hsm_profiles__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_hsm_profiles__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_hsm_profiles__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='HSM profiles for KMS providers'; + +-- KMS HSM Profile Details (Protocol-specific configuration) +CREATE TABLE IF NOT EXISTS `cloud`.`kms_hsm_profile_details` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `profile_id` BIGINT UNSIGNED NOT NULL COMMENT 'HSM profile ID', + `name` VARCHAR(255) NOT NULL COMMENT 'Config key (e.g. library_path, endpoint, pin, cert_content)', + `value` TEXT NOT NULL COMMENT 'Config value (encrypted if sensitive)', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_profile_name` (`profile_id`, `name`), + CONSTRAINT `fk_kms_hsm_profile_details__profile_id` FOREIGN KEY (`profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Details for HSM profiles (key-value configuration)'; + +-- KMS Keys (Key Encryption Key Metadata) +-- Account-scoped KEKs for envelope encryption +CREATE TABLE IF NOT EXISTS `cloud`.`kms_keys` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `uuid` VARCHAR(40) NOT NULL COMMENT 'UUID - user-facing identifier', + `name` VARCHAR(255) NOT NULL COMMENT 'User-friendly name', + `description` VARCHAR(1024) COMMENT 'User description', + `kek_label` VARCHAR(255) NOT NULL COMMENT 'Provider-specific KEK label/ID', + `purpose` VARCHAR(32) NOT NULL COMMENT 'Key purpose (VOLUME_ENCRYPTION, TLS_CERT)', + `account_id` BIGINT UNSIGNED NOT NULL COMMENT 'Owning account', + `domain_id` BIGINT UNSIGNED NOT NULL COMMENT 'Owning domain', + `zone_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zone where key is valid', + `algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding' COMMENT 'Encryption algorithm', + `key_bits` INT NOT NULL DEFAULT 256 COMMENT 'Key size in bits', + `enabled` TINYINT(1) NOT NULL DEFAULT 1 COMMENT 'Whether the key is enabled for new cryptographic operations', + `hsm_profile_id` BIGINT UNSIGNED NOT NULL COMMENT 'Current HSM profile ID for this key', + `created` DATETIME NOT NULL COMMENT 'Creation timestamp', + `removed` DATETIME COMMENT 'Removal timestamp for soft delete', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uuid` (`uuid`), + INDEX `idx_account_purpose` (`account_id`, `purpose`, `enabled`), + INDEX `idx_domain_purpose` (`domain_id`, `purpose`, `enabled`), + INDEX `idx_zone_enabled` (`zone_id`, `enabled`), + CONSTRAINT `fk_kms_keys__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_keys__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_keys__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_keys__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS Key (KEK) metadata - account-scoped keys for envelope encryption'; + +-- KMS KEK Versions (multiple KEKs per KMS key for gradual rotation) +-- Supports multiple KEK versions per logical KMS key during rotation +CREATE TABLE IF NOT EXISTS `cloud`.`kms_kek_versions` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `uuid` VARCHAR(40) NOT NULL COMMENT 'UUID', + `kms_key_id` BIGINT UNSIGNED NOT NULL COMMENT 'Reference to kms_keys table', + `version_number` INT NOT NULL COMMENT 'Version number (1, 2, 3, ...)', + `kek_label` VARCHAR(255) NOT NULL COMMENT 'Provider-specific KEK label/ID for this version', + `status` VARCHAR(32) NOT NULL DEFAULT 'Active' COMMENT 'Active, Previous, Archived', + `hsm_profile_id` BIGINT UNSIGNED COMMENT 'HSM profile where this KEK version is stored', + `created` DATETIME NOT NULL COMMENT 'Creation timestamp', + `removed` DATETIME COMMENT 'Removal timestamp for soft delete', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uuid` (`uuid`), + UNIQUE KEY `uk_kms_key_version` (`kms_key_id`, `version_number`, `removed`), + INDEX `idx_kms_key_status` (`kms_key_id`, `status`, `removed`), + INDEX `idx_kek_label` (`kek_label`), + CONSTRAINT `fk_kms_kek_versions__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_kek_versions__hsm_profile_id` FOREIGN KEY (`hsm_profile_id`) REFERENCES `kms_hsm_profiles`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KEK versions for a KMS key - supports gradual rotation'; + +-- KMS Wrapped Keys (Data Encryption Keys) +-- Generic table for wrapped DEKs - references kms_keys for metadata and kek_versions for specific KEK version +CREATE TABLE IF NOT EXISTS `cloud`.`kms_wrapped_key` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + `uuid` VARCHAR(40) NOT NULL COMMENT 'UUID', + `kms_key_id` BIGINT UNSIGNED COMMENT 'Reference to kms_keys table', + `kek_version_id` BIGINT UNSIGNED COMMENT 'Reference to kms_kek_versions table', + `zone_id` BIGINT UNSIGNED NOT NULL COMMENT 'Zone ID for zone-scoped keys', + `wrapped_blob` VARBINARY(4096) NOT NULL COMMENT 'Encrypted DEK material', + `created` DATETIME NOT NULL COMMENT 'Creation timestamp', + `removed` DATETIME COMMENT 'Removal timestamp for soft delete', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uuid` (`uuid`), + INDEX `idx_kms_key_id` (`kms_key_id`, `removed`), + INDEX `idx_kek_version_id` (`kek_version_id`, `removed`), + INDEX `idx_zone_id` (`zone_id`, `removed`), + CONSTRAINT `fk_kms_wrapped_key__kms_key_id` FOREIGN KEY (`kms_key_id`) REFERENCES `kms_keys`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_wrapped_key__kek_version_id` FOREIGN KEY (`kek_version_id`) REFERENCES `kms_kek_versions`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_kms_wrapped_key__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS wrapped encryption keys (DEKs) - references kms_keys for KEK metadata and kek_versions for specific version'; + +-- Add KMS key reference to volumes table (which KMS key was used) +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'kms_key_id', 'BIGINT UNSIGNED COMMENT ''KMS key ID used for volume encryption'''); +CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.volumes', 'fk_volumes__kms_key_id', '(kms_key_id)', '`kms_keys`(`id`)'); + +-- Add KMS wrapped key reference to volumes table +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.volumes', 'kms_wrapped_key_id', 'BIGINT UNSIGNED COMMENT ''KMS wrapped key ID for volume encryption'''); +CALL `cloud`.`IDEMPOTENT_ADD_FOREIGN_KEY`('cloud.volumes', 'fk_volumes__kms_wrapped_key_id', '(kms_wrapped_key_id)', '`kms_wrapped_key`(`id`)'); + +-- KMS Database Provider KEK Objects (PKCS#11-like object storage) +-- Stores KEKs for the database KMS provider in a PKCS#11-compatible format +CREATE TABLE IF NOT EXISTS `cloud`.`kms_database_kek_objects` ( + `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 'Object handle (PKCS#11 CKA_HANDLE)', + `uuid` VARCHAR(40) NOT NULL COMMENT 'UUID', + -- PKCS#11 Object Class (CKA_CLASS) + `object_class` VARCHAR(32) NOT NULL DEFAULT 'CKO_SECRET_KEY' COMMENT 'PKCS#11 object class (CKO_SECRET_KEY, CKO_PRIVATE_KEY, etc.)', + -- PKCS#11 Label (CKA_LABEL) - human-readable identifier + `label` VARCHAR(255) NOT NULL COMMENT 'PKCS#11 label (CKA_LABEL) - human-readable identifier', + -- PKCS#11 ID (CKA_ID) - application-defined identifier + `object_id` VARBINARY(64) COMMENT 'PKCS#11 object ID (CKA_ID) - application-defined identifier', + -- Key Type (CKA_KEY_TYPE) + `key_type` VARCHAR(32) NOT NULL DEFAULT 'CKK_AES' COMMENT 'PKCS#11 key type (CKK_AES, CKK_RSA, etc.)', + -- Key Material (CKA_VALUE) - encrypted KEK material + `key_material` VARBINARY(512) NOT NULL COMMENT 'PKCS#11 key value (CKA_VALUE) - encrypted KEK material', + -- Key Attributes (PKCS#11 boolean attributes) + `is_sensitive` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_SENSITIVE - key material is sensitive', + `is_extractable` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'PKCS#11 CKA_EXTRACTABLE - key can be extracted', + `is_token` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_TOKEN - object is on token (persistent)', + `is_private` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_PRIVATE - object is private', + `is_modifiable` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'PKCS#11 CKA_MODIFIABLE - object can be modified', + `is_copyable` BOOLEAN NOT NULL DEFAULT FALSE COMMENT 'PKCS#11 CKA_COPYABLE - object can be copied', + `is_destroyable` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_DESTROYABLE - object can be destroyed', + `always_sensitive` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_ALWAYS_SENSITIVE - key was always sensitive', + `never_extractable` BOOLEAN NOT NULL DEFAULT TRUE COMMENT 'PKCS#11 CKA_NEVER_EXTRACTABLE - key was never extractable', + -- Key Metadata + `purpose` VARCHAR(32) NOT NULL COMMENT 'Key purpose (VOLUME_ENCRYPTION, TLS_CERT)', + `key_bits` INT NOT NULL COMMENT 'Key size in bits (128, 192, 256)', + `algorithm` VARCHAR(64) NOT NULL DEFAULT 'AES/GCM/NoPadding' COMMENT 'Encryption algorithm', + -- Validity Dates (PKCS#11 CKA_START_DATE, CKA_END_DATE) + `start_date` DATETIME COMMENT 'PKCS#11 CKA_START_DATE - key validity start', + `end_date` DATETIME COMMENT 'PKCS#11 CKA_END_DATE - key validity end', + -- Lifecycle + `created` DATETIME NOT NULL COMMENT 'Creation timestamp', + `last_used` DATETIME COMMENT 'Last usage timestamp', + `removed` DATETIME COMMENT 'Removal timestamp for soft delete', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_uuid` (`uuid`), + UNIQUE KEY `uk_label_removed` (`label`, `removed`), + INDEX `idx_purpose_removed` (`purpose`, `removed`), + INDEX `idx_key_type` (`key_type`, `removed`), + INDEX `idx_object_class` (`object_class`, `removed`), + INDEX `idx_created` (`created`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='KMS Database Provider KEK Objects - PKCS#11-like object storage for KEKs'; + +--- Disable/enable NICs +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nics','enabled', 'TINYINT(1) NOT NULL DEFAULT 1 COMMENT ''Indicates whether the NIC is enabled or not'' '); + +--- Add URLs for OAuth provider +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider','authorize_url', 'VARCHAR(255) DEFAULT NULL COMMENT ''Authorize URL for OAuth initialization'' '); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.oauth_provider','token_url', 'VARCHAR(255) DEFAULT NULL COMMENT ''Token URL for OAuth finalization'' '); + +--- Quota tariff/usage mapping +CREATE TABLE IF NOT EXISTS `cloud_usage`.`quota_tariff_usage` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `tariff_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the tariff of the Quota usage detail calculated, foreign key to quota_tariff table', + `quota_usage_id` bigint(20) unsigned NOT NULL COMMENT 'ID of the aggregation of Quota usage details, foreign key to quota_usage table', + `quota_used` decimal(20,8) NOT NULL COMMENT 'Amount of quota used', + 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`)); + +--- Per-VM ISO attachments. user_vm.iso_id remains as the primary/bootable ISO pointer. +CREATE TABLE IF NOT EXISTS `cloud`.`vm_iso_map` ( + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, + `vm_id` bigint(20) unsigned NOT NULL COMMENT 'foreign key to user_vm', + `iso_id` bigint(20) unsigned NOT NULL COMMENT 'foreign key to vm_template (ISOs are templates of format ISO)', + `device_seq` int(10) unsigned NOT NULL COMMENT 'cdrom slot index used to derive the libvirt device label (3=hdc, 4=hdd)', + `created` datetime NOT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `uc_vm_iso_map__vm_iso` (`vm_id`, `iso_id`), + UNIQUE KEY `uc_vm_iso_map__vm_seq` (`vm_id`, `device_seq`), + CONSTRAINT `fk_vm_iso_map__vm_id` FOREIGN KEY (`vm_id`) REFERENCES `cloud`.`user_vm` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_vm_iso_map__iso_id` FOREIGN KEY (`iso_id`) REFERENCES `cloud`.`vm_template` (`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'; + +-- Generalise VM schedule tables into resource-agnostic resource_schedule / resource_scheduled_job. +-- Step 1: rename vm_schedule → resource_schedule, rename vm_id → resource_id, add resource_type column. +ALTER TABLE `cloud`.`vm_schedule` + DROP FOREIGN KEY `fk_vm_schedule__vm_id`, + DROP INDEX `i_vm_schedule__vm_id`, + DROP INDEX `i_vm_schedule__enabled_end_date`, + CHANGE COLUMN `vm_id` `resource_id` bigint unsigned NOT NULL COMMENT 'id of the scheduled resource', + ADD COLUMN `resource_type` varchar(64) NOT NULL DEFAULT 'VirtualMachine' COMMENT 'type of the scheduled resource' AFTER `uuid`; + +RENAME TABLE `cloud`.`vm_schedule` TO `cloud`.`resource_schedule`; + +ALTER TABLE `cloud`.`resource_schedule` + ADD INDEX `i_resource_schedule__resource` (`resource_type`, `resource_id`), + ADD INDEX `i_resource_schedule__enabled_end_date` (`enabled`, `end_date`); + +-- Step 2: rename vm_scheduled_job → resource_scheduled_job, rename columns. +ALTER TABLE `cloud`.`vm_scheduled_job` + DROP FOREIGN KEY `fk_vm_scheduled_job__vm_id`, + DROP FOREIGN KEY `fk_vm_scheduled_job__vm_schedule_id`, + DROP INDEX `i_vm_scheduled_job__vm_id`, + DROP INDEX `i_vm_scheduled_job__scheduled_timestamp`, + DROP INDEX `vm_schedule_id`, + CHANGE COLUMN `vm_id` `resource_id` bigint unsigned NOT NULL COMMENT 'id of the scheduled resource', + CHANGE COLUMN `vm_schedule_id` `schedule_id` bigint unsigned NOT NULL COMMENT 'id of the resource_schedule row', + ADD COLUMN `resource_type` varchar(64) NOT NULL DEFAULT 'VirtualMachine' COMMENT 'type of the scheduled resource' AFTER `uuid`; + +RENAME TABLE `cloud`.`vm_scheduled_job` TO `cloud`.`resource_scheduled_job`; + +ALTER TABLE `cloud`.`resource_scheduled_job` + ADD UNIQUE KEY `uc_resource_scheduled_job__schedule_timestamp` (`schedule_id`, `scheduled_timestamp`), + ADD INDEX `i_resource_scheduled_job__resource` (`resource_type`, `resource_id`), + ADD INDEX `i_resource_scheduled_job__scheduled_timestamp` (`scheduled_timestamp`), + ADD CONSTRAINT `fk_resource_scheduled_job__schedule_id` FOREIGN KEY (`schedule_id`) REFERENCES `resource_schedule`(`id`) ON DELETE CASCADE; + +-- Step 3: details table for action-specific parameters (used by the generic resource schedule API in Commit 2). +CREATE TABLE IF NOT EXISTS `cloud`.`resource_schedule_details` ( + `id` bigint unsigned NOT NULL auto_increment, + `schedule_id` bigint unsigned NOT NULL COMMENT 'id of the resource_schedule row', + `name` varchar(255) NOT NULL, + `value` varchar(1024) NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'should this detail be visible to the end user', + PRIMARY KEY (`id`), + INDEX `i_resource_schedule_details__schedule_id` (`schedule_id`), + CONSTRAINT `fk_resource_schedule_details__schedule_id` FOREIGN KEY (`schedule_id`) REFERENCES `resource_schedule`(`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Step 4: rename CRUD event types from VM.SCHEDULE.{CREATE,UPDATE,DELETE} to the new generic SCHEDULE.{CREATE,UPDATE,DELETE}. +-- Action-execution events (VM.SCHEDULE.START, .STOP, .REBOOT, .FORCE_STOP, .FORCE_REBOOT) keep their existing names. +UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.CREATE' WHERE `type` = 'VM.SCHEDULE.CREATE'; +UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.UPDATE' WHERE `type` = 'VM.SCHEDULE.UPDATE'; +UPDATE `cloud`.`event` SET `type` = 'SCHEDULE.DELETE' WHERE `type` = 'VM.SCHEDULE.DELETE'; + +-- Step 5: Rename the global configuration key for the scheduler +UPDATE `cloud`.`configuration` SET name='scheduler.jobs.expire.interval' WHERE name='vmscheduler.jobs.expire.interval'; + +-- Remove stale realhostip.com default values; domain has been dead since ~2015. +UPDATE `cloud`.`configuration` + SET value = NULL + WHERE name IN ('consoleproxy.url.domain', 'secstorage.ssl.cert.domain') + AND value IN ('realhostip.com', '*.realhostip.com'); + +-- Add management_server_details table to allow ManagementServer scope configs +CREATE TABLE IF NOT EXISTS `management_server_details` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `management_server_id` bigint unsigned NOT NULL COMMENT 'management server the detail is related to', + `name` varchar(255) NOT NULL COMMENT 'name of the detail', + `value` varchar(255) NOT NULL, + `display` tinyint(1) NOT NULL DEFAULT '1' COMMENT 'True if the detail can be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_management_server_details__management_server_id` FOREIGN KEY `fk_management_server_details__management_server_id`(`management_server_id`) REFERENCES `mshost`(`id`) ON DELETE CASCADE, + KEY `i_management_server_details__name__value` (`name`(128),`value`(128)) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +-- Add checkpoint tracking fields to backups table for incremental backup support +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'from_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "Previous active checkpoint id for incremental backups"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'to_checkpoint_id', 'VARCHAR(255) DEFAULT NULL COMMENT "New checkpoint id created for the next incremental backup"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'checkpoint_create_time', 'BIGINT DEFAULT NULL COMMENT "Checkpoint creation timestamp from libvirt"'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'host_id', 'BIGINT UNSIGNED DEFAULT NULL COMMENT "Host where backup is running"'); + +-- Create image_transfer table for per-disk image transfers +CREATE TABLE IF NOT EXISTS `cloud`.`image_transfer`( + `id` bigint unsigned NOT NULL auto_increment COMMENT 'id', + `uuid` varchar(40) NOT NULL COMMENT 'uuid', + `account_id` bigint unsigned NOT NULL COMMENT 'Account ID', + `domain_id` bigint unsigned NOT NULL COMMENT 'Domain ID', + `data_center_id` bigint unsigned NOT NULL COMMENT 'Data Center ID', + `backup_id` bigint unsigned COMMENT 'Backup ID', + `volume_id` bigint unsigned NOT NULL COMMENT 'Volume ID', + `host_id` bigint unsigned NOT NULL COMMENT 'Host ID', + `transfer_url` varchar(255) COMMENT 'ImageIO transfer URL', + `file` varchar(255) COMMENT 'File for the file backend', + `phase` varchar(20) NOT NULL COMMENT 'Transfer phase: initializing, transferring, finished, failed', + `socket` varchar(255) COMMENT 'Unix socket for nbd backend', + `direction` varchar(20) NOT NULL COMMENT 'Direction: upload, download', + `backend` varchar(20) NOT NULL COMMENT 'Backend: nbd, file', + `progress` int COMMENT 'Transfer progress percentage (0-100)', + `signed_ticket_id` varchar(255) COMMENT 'Signed ticket ID from ImageIO', + `created` datetime NOT NULL COMMENT 'date created', + `updated` datetime COMMENT 'date updated if not null', + `removed` datetime COMMENT 'date removed if not null', + PRIMARY KEY (`id`), + UNIQUE KEY `uuid` (`uuid`), + CONSTRAINT `fk_image_transfer__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_image_transfer__volume_id` FOREIGN KEY (`volume_id`) REFERENCES `volumes`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_image_transfer__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE, + INDEX `i_image_transfer__backup_id`(`backup_id`) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +--- Quota resource statement +INSERT INTO cloud.role_permissions (uuid, role_id, rule, permission, sort_order) +SELECT uuid(), role_id, 'quotaResourceStatement', permission, sort_order +FROM cloud.role_permissions rp +WHERE rule = 'quotaStatement' AND NOT EXISTS(SELECT 1 FROM cloud.role_permissions rp_ WHERE rp.role_id = rp_.role_id AND rp_.rule = 'quotaResourceStatement'); + +-- Increase length of value of extension details from 255 to 4096 to support longer details value +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.extension_details', 'value', 'value', 'VARCHAR(4096)'); +CALL `cloud`.`IDEMPOTENT_CHANGE_COLUMN`('cloud.extension_resource_map_details', 'value', 'value', 'VARCHAR(4096)'); + +-- Add CustomAction service support to physical_network_service_providers +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.physical_network_service_providers', 'custom_action_service_provided', 'tinyint(1) unsigned NOT NULL DEFAULT 0 COMMENT "Is Custom Action service provided" AFTER `networkacl_service_provided`'); + +--- Gui theme login base domain +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.gui_themes', 'login_base_domain', 'TEXT DEFAULT NULL'); + +-- Add description for secondary IP addresses +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.nic_secondary_ips', 'description', 'VARCHAR(2048) DEFAULT NULL'); + +--- Change nw_rate and mc_rate column types from smallint unsigned to int unsigned to support larger rate values +ALTER TABLE `cloud`.`service_offering` + MODIFY COLUMN `nw_rate` int unsigned DEFAULT 200 COMMENT 'network rate throttle mbits/s', + MODIFY COLUMN `mc_rate` int unsigned DEFAULT 10 COMMENT 'mcast rate throttle mbits/s'; + +ALTER TABLE `cloud`.`network_offerings` + MODIFY COLUMN `nw_rate` int unsigned COMMENT 'network rate throttle mbits/s', + MODIFY COLUMN `mc_rate` int unsigned COMMENT 'mcast rate throttle mbits/s'; + +-- Soft delete port forwarding, load balancing and firewall rules +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.firewall_rules', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.load_balancer_vm_map', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.load_balancer_cert_map', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.load_balancer_healthcheck_policies', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.load_balancer_stickiness_policies', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.global_load_balancer_lb_rule_map', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.elastic_lb_vm_map', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.tungsten_lb_health_monitor', 'removed', 'datetime DEFAULT NULL'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.global_load_balancing_rules', 'removed', 'datetime DEFAULT NULL'); + +ALTER TABLE `cloud`.`load_balancer_vm_map` +DROP KEY `load_balancer_id`, +ADD UNIQUE KEY `load_balancer_id` (`load_balancer_id`, `instance_id`, `instance_ip`, `removed`); + +ALTER TABLE `cloud`.`global_load_balancer_lb_rule_map` +DROP KEY `gslb_rule_id`, +ADD UNIQUE KEY `gslb_rule_id` (`gslb_rule_id`, `lb_rule_id`, `removed`); + +-- ====================================================================== +-- DNS Framework Schema +-- ====================================================================== + +-- DNS Server Table (Stores DNS Server Configurations) +CREATE TABLE IF NOT EXISTS `cloud`.`dns_server` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns server', + `uuid` varchar(40) COMMENT 'uuid of the dns server', + `name` varchar(255) NOT NULL COMMENT 'display name of the dns server', + `provider_type` varchar(255) NOT NULL COMMENT 'Provider type such as PowerDns', + `url` varchar(1024) NOT NULL COMMENT 'dns server url', + `dns_username` varchar(255) COMMENT 'username or email for dns server credentials', + `dns_api_key` varchar(255) NOT NULL COMMENT 'api key or token for the dns server ', + `port` int(11) DEFAULT NULL COMMENT 'optional dns server port', + `name_servers` varchar(1024) DEFAULT NULL COMMENT 'Comma separated list of name servers', + `is_public` tinyint(1) NOT NULL DEFAULT '0', + `public_domain_suffix` VARCHAR(255), + `state` ENUM('Enabled', 'Disabled') NOT NULL DEFAULT 'Disabled', + `domain_id` bigint unsigned COMMENT 'for domain-specific ownership', + `account_id` bigint(20) unsigned NOT NULL, + `created` datetime NOT NULL COMMENT 'date created', + `removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)', + PRIMARY KEY (`id`), + KEY `i_dns_server__account_id` (`account_id`), + CONSTRAINT `fk_dns_server__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- DNS Server Details Table +CREATE TABLE IF NOT EXISTS `cloud`.`dns_server_details` ( + `id` bigint unsigned UNIQUE NOT NULL AUTO_INCREMENT COMMENT 'id', + `dns_server_id` bigint unsigned NOT NULL COMMENT 'dns_server the detail is related to', + `name` varchar(255) NOT NULL COMMENT 'name of the detail', + `value` varchar(255) NOT NULL COMMENT 'value of the detail', + `display` tinyint(1) NOT NULL DEFAULT 1 COMMENT 'Should detail be displayed to the end user', + PRIMARY KEY (`id`), + CONSTRAINT `fk_dns_server_details__dns_server_id` FOREIGN KEY (`dns_server_id`) REFERENCES `dns_server`(`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- DNS Zone Table (Stores DNS Zone Metadata) +CREATE TABLE IF NOT EXISTS `cloud`.`dns_zone` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns zone', + `uuid` varchar(40) COMMENT 'uuid of the dns zone', + `name` varchar(255) NOT NULL COMMENT 'dns zone name (e.g. example.com)', + `dns_server_id` bigint unsigned NOT NULL COMMENT 'fk to dns_server.id', + `external_reference` VARCHAR(255) COMMENT 'id of external provider resource', + `domain_id` bigint unsigned COMMENT 'for domain-specific ownership', + `account_id` bigint unsigned COMMENT 'account id. foreign key to account table', + `description` varchar(1024) DEFAULT NULL, + `type` ENUM('Private', 'Public') NOT NULL DEFAULT 'Public', + `state` ENUM('Active', 'Inactive') NOT NULL DEFAULT 'Inactive', + `created` datetime NOT NULL COMMENT 'date created', + `removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)', + PRIMARY KEY (`id`), + CONSTRAINT `uc_dns_zone__uuid` UNIQUE (`uuid`), + KEY `i_dns_zone__dns_server` (`dns_server_id`), + KEY `i_dns_zone__account_id` (`account_id`), + CONSTRAINT `fk_dns_zone__dns_server_id` FOREIGN KEY (`dns_server_id`) REFERENCES `dns_server` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_dns_zone__account_id` FOREIGN KEY (`account_id`) REFERENCES `account` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_dns_zone__domain_id` FOREIGN KEY (`domain_id`) REFERENCES `domain` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- DNS Zone Network Map (One-to-Many Link) +CREATE TABLE IF NOT EXISTS `cloud`.`dns_zone_network_map` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id of the dns zone to network mapping', + `uuid` varchar(40), + `dns_zone_id` bigint(20) unsigned NOT NULL, + `network_id` bigint(20) unsigned NOT NULL COMMENT 'network to which dns zone is associated to', + `sub_domain` varchar(255) DEFAULT NULL COMMENT 'Subdomain for auto-registration', + `created` datetime NOT NULL COMMENT 'date created', + `removed` datetime DEFAULT NULL COMMENT 'Date removed (soft delete)', + PRIMARY KEY (`id`), + CONSTRAINT `uc_dns_zone_network_map__uuid` UNIQUE (`uuid`), + KEY `fk_dns_map__zone_id` (`dns_zone_id`), + KEY `fk_dns_map__network_id` (`network_id`), + CONSTRAINT `fk_dns_map__zone_id` FOREIGN KEY (`dns_zone_id`) REFERENCES `dns_zone` (`id`) ON DELETE CASCADE, + CONSTRAINT `fk_dns_map__network_id` FOREIGN KEY (`network_id`) REFERENCES `networks` (`id`) ON DELETE CASCADE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- This is part of allowing firewall rules on public IP addresses in VPC network +ALTER TABLE `cloud`.`firewall_rules` MODIFY COLUMN `network_id` BIGINT UNSIGNED NULL; + +-- KBOSS + +CREATE TABLE IF NOT EXISTS `cloud`.`internal_backup_pool_ref` ( + `id` bigint NOT NULL UNIQUE AUTO_INCREMENT, + `backup_id` bigint unsigned NOT NULL COMMENT 'The backup ID. Foreign key that points to the backups table.', + `storage_pool_id` bigint unsigned NOT NULL COMMENT 'The storage ID. Foreign key that points to the storage_pool table.', + `volume_id` bigint unsigned NOT NULL COMMENT 'The volumes ID. Foreign key that points to the volumes table.', + `backup_delta_path` varchar(255) COMMENT 'Path of the created delta.', + `backup_parent_path` varchar(255) COMMENT 'Path of the created delta parent.', + PRIMARY KEY (`id`), + CONSTRAINT `fk_internal_backup_pool_ref__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`), + CONSTRAINT `fk_internal_backup_pool_ref__storage_pool_id` FOREIGN KEY (`storage_pool_id`) REFERENCES `storage_pool`(`id`), + CONSTRAINT `fk_internal_backup_pool_ref__volume_id` FOREIGN KEY (`volume_id`) REFERENCES `volumes`(`id`) + ); + +CREATE TABLE IF NOT EXISTS `cloud`.`internal_backup_store_ref` ( + `id` bigint NOT NULL UNIQUE AUTO_INCREMENT, + `backup_id` bigint unsigned NOT NULL COMMENT 'The backup ID. Foreign key that points to the backups table.', + `volume_id` bigint unsigned NOT NULL COMMENT 'The volume ID. Foreign key that points to the volumes table.', + `device_id` bigint unsigned COMMENT 'device ID of the volume', + `path` varchar(255) COMMENT 'Path of the backup.', + PRIMARY KEY (`id`), + CONSTRAINT `fk_internal_backup_store_ref__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`), + CONSTRAINT `fk_internal_backup_store_ref__volume_id` FOREIGN KEY (`volume_id`) REFERENCES `volumes`(`id`) + ); + +CREATE TABLE IF NOT EXISTS `cloud`.`internal_backup_service_job` ( + `id` bigint NOT NULL UNIQUE AUTO_INCREMENT, + `backup_id` bigint unsigned NOT NULL COMMENT 'The backup ID. Foreign key that points to the backups table.', + `instance_id` bigint unsigned NOT NULL COMMENT 'The instance ID. Foreign key that points to the vm_instance table.', + `account_id` bigint(20) unsigned COMMENT 'Account ID of the owner of the VM.', + `host_id` bigint unsigned COMMENT 'The host ID that is executing the compression. Foreign key that points to the host table.', + `zone_id` bigint unsigned NOT NULL COMMENT 'The zone ID of the where the VM is. Foreign key that points to the data_center table', + `attempts` int(32) unsigned NOT NULL DEFAULT 0, + `type` varchar(55) NOT NULL, + `created` datetime NOT NULL, + `scheduled_start_time` datetime NOT NULL, + `start_time` datetime, + `removed` datetime, + PRIMARY KEY (`id`), + CONSTRAINT `fk_internal_backup_service_job__backup_id` FOREIGN KEY (`backup_id`) REFERENCES `backups`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_internal_backup_service_job__instance_id` FOREIGN KEY (`instance_id`) REFERENCES `vm_instance`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_internal_backup_service_job__host_id` FOREIGN KEY (`host_id`) REFERENCES `host`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_internal_backup_service_job__zone_id` FOREIGN KEY (`zone_id`) REFERENCES `data_center`(`id`) ON DELETE CASCADE, + CONSTRAINT `fk_internal_backup_service_job__account_id` FOREIGN KEY (`account_id`) REFERENCES `account`(`id`) ON DELETE CASCADE + ); + +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'uncompressed_size', 'bigint unsigned'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'compression_status', 'varchar(55)'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backups', 'validation_status', 'varchar(55)'); +CALL `cloud`.`IDEMPOTENT_ADD_COLUMN`('cloud.backup_schedule', 'isolated', 'TINYINT(1) NOT NULL DEFAULT 0 COMMENT "Whether the scheduled backups will be isolated or not."'); + +UPDATE `cloud`.`configuration` SET `value`=CONCAT(`value`, ', backupValidationCommandTimeout, backupValidationScreenshotWait, backupValidationBootTimeout') +WHERE `name`='user.vm.readonly.details' AND `value` IS NOT NULL; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql index dc64380fb57b..edc164c40cbd 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.account_view.sql @@ -64,10 +64,20 @@ select `cpucount`.`count` AS `cpuTotal`, `memorylimit`.`max` AS `memoryLimit`, `memorycount`.`count` AS `memoryTotal`, + `gpulimit`.`max` AS `gpuLimit`, + `gpucount`.`count` AS `gpuTotal`, `primary_storage_limit`.`max` AS `primaryStorageLimit`, `primary_storage_count`.`count` AS `primaryStorageTotal`, `secondary_storage_limit`.`max` AS `secondaryStorageLimit`, `secondary_storage_count`.`count` AS `secondaryStorageTotal`, + `backup_limit`.`max` AS `backupLimit`, + `backup_count`.`count` AS `backupTotal`, + `backup_storage_limit`.`max` AS `backupStorageLimit`, + `backup_storage_count`.`count` AS `backupStorageTotal`, + `bucket_limit`.`max` AS `bucketLimit`, + `bucket_count`.`count` AS `bucketTotal`, + `object_storage_limit`.`max` AS `objectStorageLimit`, + `object_storage_count`.`count` AS `objectStorageTotal`, `async_job`.`id` AS `job_id`, `async_job`.`uuid` AS `job_uuid`, `async_job`.`job_status` AS `job_status`, @@ -148,6 +158,12 @@ from `cloud`.`resource_count` memorycount ON account.id = memorycount.account_id and memorycount.type = 'memory' and memorycount.tag IS NULL left join + `cloud`.`resource_limit` gpulimit ON account.id = gpulimit.account_id + and gpulimit.type = 'gpu' and gpulimit.tag IS NULL + left join + `cloud`.`resource_count` gpucount ON account.id = gpucount.account_id + and gpucount.type = 'gpu' and gpucount.tag IS NULL + left join `cloud`.`resource_limit` primary_storage_limit ON account.id = primary_storage_limit.account_id and primary_storage_limit.type = 'primary_storage' and primary_storage_limit.tag IS NULL left join @@ -160,6 +176,30 @@ from `cloud`.`resource_count` secondary_storage_count ON account.id = secondary_storage_count.account_id and secondary_storage_count.type = 'secondary_storage' left join + `cloud`.`resource_limit` backup_limit ON account.id = backup_limit.account_id + and backup_limit.type = 'backup' + left join + `cloud`.`resource_count` backup_count ON account.id = backup_count.account_id + and backup_count.type = 'backup' + left join + `cloud`.`resource_limit` backup_storage_limit ON account.id = backup_storage_limit.account_id + and backup_storage_limit.type = 'backup_storage' + left join + `cloud`.`resource_count` backup_storage_count ON account.id = backup_storage_count.account_id + and backup_storage_count.type = 'backup_storage' + left join + `cloud`.`resource_limit` bucket_limit ON account.id = bucket_limit.account_id + and bucket_limit.type = 'bucket' + left join + `cloud`.`resource_count` bucket_count ON account.id = bucket_count.account_id + and bucket_count.type = 'bucket' + left join + `cloud`.`resource_limit` object_storage_limit ON account.id = object_storage_limit.account_id + and object_storage_limit.type = 'object_storage' + left join + `cloud`.`resource_count` object_storage_count ON account.id = object_storage_count.account_id + and object_storage_count.type = 'object_storage' + left join `cloud`.`async_job` ON async_job.instance_id = account.id and async_job.instance_type = 'Account' and async_job.job_status = 0; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.data_center_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.data_center_view.sql index c34df4f1cbf5..46aea863fc5e 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.data_center_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.data_center_view.sql @@ -42,6 +42,7 @@ select data_center.type, data_center.removed, data_center.sort_key, + data_center.storage_access_groups, domain.id domain_id, domain.uuid domain_uuid, domain.name domain_name, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.dns_server_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.dns_server_view.sql new file mode 100644 index 000000000000..a1bc1a1141a9 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.dns_server_view.sql @@ -0,0 +1,44 @@ +-- 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. + +-- VIEW `cloud`.`dns_server_view`; + +DROP VIEW IF EXISTS `cloud`.`dns_server_view`; +CREATE VIEW `cloud`.`dns_server_view` AS + SELECT + dns.id, + dns.uuid, + dns.name, + dns.provider_type, + dns.url, + dns.port, + dns.name_servers, + dns.is_public, + dns.public_domain_suffix, + dns.state, + dns.created, + dns.removed, + account.account_name account_name, + domain.name domain_name, + domain.uuid domain_uuid, + domain.path domain_path + FROM + `cloud`.`dns_server` dns + INNER JOIN + `cloud`.`account` account ON dns.account_id = account.id + INNER JOIN + `cloud`.`domain` domain ON dns.domain_id = domain.id; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.dns_zone_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.dns_zone_view.sql new file mode 100644 index 000000000000..a41e003ae4f2 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.dns_zone_view.sql @@ -0,0 +1,45 @@ +-- 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. + +-- VIEW `cloud`.`dns_zone_view`; + +DROP VIEW IF EXISTS `cloud`.`dns_zone_view`; +CREATE VIEW `cloud`.`dns_zone_view` AS + SELECT + zone.id, + zone.uuid, + zone.name, + zone.dns_server_id, + zone.state, + zone.description, + server.uuid dns_server_uuid, + server.name dns_server_name, + server_account.account_name dns_server_account_name, + account.account_name account_name, + domain.name domain_name, + domain.uuid domain_uuid, + domain.path domain_path + FROM + `cloud`.`dns_zone` zone + INNER JOIN + `cloud`.`dns_server` server ON zone.dns_server_id = server.id + INNER JOIN + `cloud`.`account` server_account ON server.account_id = server_account.id + INNER JOIN + `cloud`.`account` account ON zone.account_id = account.id + INNER JOIN + `cloud`.`domain` domain ON zone.domain_id = domain.id; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql index a12e02bcfdb8..d5f17606cb41 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_router_view.sql @@ -76,6 +76,7 @@ select nics.broadcast_uri broadcast_uri, nics.isolation_uri isolation_uri, nics.mtu mtu, + nics.enabled is_nic_enabled, vpc.id vpc_id, vpc.uuid vpc_uuid, vpc.name vpc_name, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_view.sql index 201ece95023a..14fd87536aa1 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.domain_view.sql @@ -55,10 +55,20 @@ select `cpucount`.`count` AS `cpuTotal`, `memorylimit`.`max` AS `memoryLimit`, `memorycount`.`count` AS `memoryTotal`, + `gpulimit`.`max` AS `gpuLimit`, + `gpucount`.`count` AS `gpuTotal`, `primary_storage_limit`.`max` AS `primaryStorageLimit`, `primary_storage_count`.`count` AS `primaryStorageTotal`, `secondary_storage_limit`.`max` AS `secondaryStorageLimit`, - `secondary_storage_count`.`count` AS `secondaryStorageTotal` + `secondary_storage_count`.`count` AS `secondaryStorageTotal`, + `backup_limit`.`max` AS `backupLimit`, + `backup_count`.`count` AS `backupTotal`, + `backup_storage_limit`.`max` AS `backupStorageLimit`, + `backup_storage_count`.`count` AS `backupStorageTotal`, + `bucket_limit`.`max` AS `bucketLimit`, + `bucket_count`.`count` AS `bucketTotal`, + `object_storage_limit`.`max` AS `objectStorageLimit`, + `object_storage_count`.`count` AS `objectStorageTotal` from `cloud`.`domain` left join @@ -122,6 +132,12 @@ from `cloud`.`resource_count` memorycount ON domain.id = memorycount.domain_id and memorycount.type = 'memory' and memorycount.tag IS NULL left join + `cloud`.`resource_limit` gpulimit ON domain.id = gpulimit.domain_id + and gpulimit.type = 'gpu' and gpulimit.tag IS NULL + left join + `cloud`.`resource_count` gpucount ON domain.id = gpucount.domain_id + and gpucount.type = 'gpu' and gpucount.tag IS NULL + left join `cloud`.`resource_limit` primary_storage_limit ON domain.id = primary_storage_limit.domain_id and primary_storage_limit.type = 'primary_storage' and primary_storage_limit.tag IS NULL left join @@ -132,4 +148,28 @@ from and secondary_storage_limit.type = 'secondary_storage' left join `cloud`.`resource_count` secondary_storage_count ON domain.id = secondary_storage_count.domain_id - and secondary_storage_count.type = 'secondary_storage'; + and secondary_storage_count.type = 'secondary_storage' + left join + `cloud`.`resource_limit` backup_limit ON domain.id = backup_limit.domain_id + and backup_limit.type = 'backup' + left join + `cloud`.`resource_count` backup_count ON domain.id = backup_count.domain_id + and backup_count.type = 'backup' + left join + `cloud`.`resource_limit` backup_storage_limit ON domain.id = backup_storage_limit.domain_id + and backup_storage_limit.type = 'backup_storage' + left join + `cloud`.`resource_count` backup_storage_count ON domain.id = backup_storage_count.domain_id + and backup_storage_count.type = 'backup_storage' + left join + `cloud`.`resource_limit` bucket_limit ON domain.id = bucket_limit.domain_id + and bucket_limit.type = 'bucket' + left join + `cloud`.`resource_count` bucket_count ON domain.id = bucket_count.domain_id + and bucket_count.type = 'bucket' + left join + `cloud`.`resource_limit` object_storage_limit ON domain.id = object_storage_limit.domain_id + and object_storage_limit.type = 'object_storage' + left join + `cloud`.`resource_count` object_storage_count ON domain.id = object_storage_count.domain_id + and object_storage_count.type = 'object_storage'; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql new file mode 100644 index 000000000000..5f49cffbf1f5 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.gui_themes_view.sql @@ -0,0 +1,39 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- VIEW `cloud`.`gui_themes_view`; + +DROP VIEW IF EXISTS `cloud`.`gui_themes_view`; + +CREATE VIEW `cloud`.`gui_themes_view` AS +SELECT + `cloud`.`gui_themes`.`id` AS `id`, + `cloud`.`gui_themes`.`uuid` AS `uuid`, + `cloud`.`gui_themes`.`name` AS `name`, + `cloud`.`gui_themes`.`description` AS `description`, + `cloud`.`gui_themes`.`css` AS `css`, + `cloud`.`gui_themes`.`json_configuration` AS `json_configuration`, + `cloud`.`gui_themes`.`login_base_domain` AS `login_base_domain`, + (SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'commonName' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) common_names, + (SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'domain' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) domains, + (SELECT group_concat(gtd.`value` separator ',') FROM `cloud`.`gui_themes_details` gtd WHERE gtd.`type` = 'account' AND gtd.gui_theme_id = `cloud`.`gui_themes`.`id`) accounts, + `cloud`.`gui_themes`.`recursive_domains` AS `recursive_domains`, + `cloud`.`gui_themes`.`is_public` AS `is_public`, + `cloud`.`gui_themes`.`created` AS `created`, + `cloud`.`gui_themes`.`removed` AS `removed` +FROM `cloud`.`gui_themes` LEFT JOIN `cloud`.`gui_themes_details` ON `cloud`.`gui_themes_details`.`gui_theme_id` = `cloud`.`gui_themes`.`id` +GROUP BY `cloud`.`gui_themes`.`id`; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.host_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.host_view.sql index 6fc8fb803862..255ce866c427 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.host_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.host_view.sql @@ -42,17 +42,21 @@ SELECT host.speed, host.ram, host.arch, + host.storage_access_groups, cluster.id cluster_id, cluster.uuid cluster_uuid, cluster.name cluster_name, cluster.cluster_type, + cluster.storage_access_groups AS cluster_storage_access_groups, data_center.id data_center_id, data_center.uuid data_center_uuid, data_center.name data_center_name, + data_center.storage_access_groups AS zone_storage_access_groups, data_center.networktype data_center_type, host_pod_ref.id pod_id, host_pod_ref.uuid pod_uuid, host_pod_ref.name pod_name, + host_pod_ref.storage_access_groups AS pod_storage_access_groups, GROUP_CONCAT(DISTINCT(host_tags.tag)) AS tag, GROUP_CONCAT(DISTINCT(explicit_host_tags.tag)) AS explicit_tag, GROUP_CONCAT(DISTINCT(implicit_host_tags.tag)) AS implicit_tag, @@ -60,6 +64,7 @@ SELECT guest_os_category.id guest_os_category_id, guest_os_category.uuid guest_os_category_uuid, guest_os_category.name guest_os_category_name, + (SELECT `value` FROM `cloud`.`host_details` `hd` WHERE `hd`.`host_id` = `cloud`.`host`.`id` AND `hd`.`name` = 'guest.os.rule') AS `guest_os_rule`, mem_caps.used_capacity memory_used_capacity, mem_caps.reserved_capacity memory_reserved_capacity, cpu_caps.used_capacity cpu_used_capacity, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql new file mode 100644 index 000000000000..9e6be1fc5b7f --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.internal_backup_view.sql @@ -0,0 +1,51 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- VIEW `cloud`.`internal_backup_view`; + +DROP VIEW IF EXISTS `cloud`.`internal_backup_view`; +CREATE VIEW `cloud`.`internal_backup_view` AS +SELECT b.id, + b.uuid, + b.vm_id, + b.backed_volumes, + b.type, + b.date, + b.status, + b.compression_status, + b.backup_offering_id, + b.size, + b.protected_size, + b.zone_id, + MAX(CASE WHEN bd.name = 'image_store_id' THEN bd.value END) image_store_id, + MAX(CASE WHEN bd.name = 'parent_id' THEN bd.value END) parent_id, + MAX(CASE WHEN bd.name = 'end_of_chain' THEN bd.value END) end_of_chain, + MAX(CASE WHEN bd.name = 'current' THEN bd.value END) current, + COALESCE(MAX(CASE WHEN bd.name = 'isolated' THEN bd.value END), 'false') isolated, + nbpr.volume_id, + nbpr.backup_delta_path storage_pool_delta_path, + nbpr.backup_parent_path storage_pool_parent_path, + nbsr.path image_store_path, + bs.id schedule_id +FROM backups b +LEFT JOIN backup_details bd ON b.id = bd.backup_id +LEFT JOIN backup_offering bo ON b.backup_offering_id = bo.id +LEFT JOIN internal_backup_store_ref nbsr ON b.id = nbsr.backup_id +LEFT JOIN internal_backup_pool_ref nbpr ON nbpr.volume_id = nbsr.volume_id and nbpr.backup_id = b.id +LEFT JOIN backup_schedule bs ON bs.id = b.backup_schedule_id +WHERE bo.provider='kboss' +GROUP BY b.id, nbsr.volume_id; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql index 640b2397a461..368566c32b32 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.network_offering_view.sql @@ -59,8 +59,6 @@ SELECT `network_offerings`.`supports_public_access` AS `supports_public_access`, `network_offerings`.`supports_vm_autoscaling` AS `supports_vm_autoscaling`, `network_offerings`.`for_vpc` AS `for_vpc`, - `network_offerings`.`for_tungsten` AS `for_tungsten`, - `network_offerings`.`for_nsx` AS `for_nsx`, `network_offerings`.`network_mode` AS `network_mode`, `network_offerings`.`service_package_id` AS `service_package_id`, `network_offerings`.`routing_mode` AS `routing_mode`, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql index c894429adf80..eb987af3ffb6 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.service_offering_view.sql @@ -71,6 +71,20 @@ SELECT `service_offering`.`dynamic_scaling_enabled` AS `dynamic_scaling_enabled`, `service_offering`.`disk_offering_strictness` AS `disk_offering_strictness`, `vsphere_storage_policy`.`value` AS `vsphere_storage_policy`, + `lease_duration_details`.`value` AS `lease_duration`, + `lease_expiry_action_details`.`value` AS `lease_expiry_action`, + `gpu_card`.`id` AS `gpu_card_id`, + `gpu_card`.`uuid` AS `gpu_card_uuid`, + `gpu_card`.`name` AS `gpu_card_name`, + `vgpu_profile`.`id` AS `vgpu_profile_id`, + `vgpu_profile`.`uuid` AS `vgpu_profile_uuid`, + `vgpu_profile`.`name` AS `vgpu_profile_name`, + `vgpu_profile`.`video_ram` AS `vgpu_profile_video_ram`, + `vgpu_profile`.`max_heads` AS `vgpu_profile_max_heads`, + `vgpu_profile`.`max_resolution_x` AS `vgpu_profile_max_resolution_x`, + `vgpu_profile`.`max_resolution_y` AS `vgpu_profile_max_resolution_y`, + `service_offering`.`gpu_count` AS `gpu_count`, + `service_offering`.`gpu_display` AS `gpu_display`, GROUP_CONCAT(DISTINCT(domain.id)) AS domain_id, GROUP_CONCAT(DISTINCT(domain.uuid)) AS domain_uuid, GROUP_CONCAT(DISTINCT(domain.name)) AS domain_name, @@ -87,6 +101,10 @@ FROM INNER JOIN `cloud`.`disk_offering` ON service_offering.disk_offering_id = disk_offering.id LEFT JOIN + `cloud`.`vgpu_profile` ON service_offering.vgpu_profile_id = vgpu_profile.id + LEFT JOIN + `cloud`.`gpu_card` ON vgpu_profile.card_id = gpu_card.id + LEFT JOIN `cloud`.`service_offering_details` AS `domain_details` ON `domain_details`.`service_offering_id` = `service_offering`.`id` AND `domain_details`.`name`='domainid' LEFT JOIN `cloud`.`domain` AS `domain` ON FIND_IN_SET(`domain`.`id`, `domain_details`.`value`) @@ -109,5 +127,11 @@ FROM LEFT JOIN `cloud`.`service_offering_details` AS `vsphere_storage_policy` ON `vsphere_storage_policy`.`service_offering_id` = `service_offering`.`id` AND `vsphere_storage_policy`.`name` = 'storagepolicy' + LEFT JOIN + `cloud`.`service_offering_details` AS `lease_duration_details` ON `lease_duration_details`.`service_offering_id` = `service_offering`.`id` + AND `lease_duration_details`.`name` = 'leaseduration' + LEFT JOIN + `cloud`.`service_offering_details` AS `lease_expiry_action_details` ON `lease_expiry_action_details`.`service_offering_id` = `service_offering`.`id` + AND `lease_expiry_action_details`.`name` = 'leaseexpiryaction' GROUP BY `service_offering`.`id`; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql index 5d7585baa3b4..641017bdd5ba 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.storage_pool_view.sql @@ -51,6 +51,7 @@ SELECT `host_pod_ref`.`name` AS `pod_name`, `storage_pool_tags`.`tag` AS `tag`, `storage_pool_tags`.`is_tag_a_rule` AS `is_tag_a_rule`, + `storage_pool_and_access_group_map`.`storage_access_group` AS `storage_access_group`, `op_host_capacity`.`used_capacity` AS `disk_used_capacity`, `op_host_capacity`.`reserved_capacity` AS `disk_reserved_capacity`, `async_job`.`id` AS `job_id`, @@ -58,13 +59,16 @@ SELECT `async_job`.`job_status` AS `job_status`, `async_job`.`account_id` AS `job_account_id` FROM - ((((((`cloud`.`storage_pool` - LEFT JOIN `cloud`.`cluster` ON ((`storage_pool`.`cluster_id` = `cluster`.`id`))) - LEFT JOIN `cloud`.`data_center` ON ((`storage_pool`.`data_center_id` = `data_center`.`id`))) - LEFT JOIN `cloud`.`host_pod_ref` ON ((`storage_pool`.`pod_id` = `host_pod_ref`.`id`))) - LEFT JOIN `cloud`.`storage_pool_tags` ON (((`storage_pool_tags`.`pool_id` = `storage_pool`.`id`)))) - LEFT JOIN `cloud`.`op_host_capacity` ON (((`storage_pool`.`id` = `op_host_capacity`.`host_id`) - AND (`op_host_capacity`.`capacity_type` IN (3 , 9))))) - LEFT JOIN `cloud`.`async_job` ON (((`async_job`.`instance_id` = `storage_pool`.`id`) - AND (`async_job`.`instance_type` = 'StoragePool') - AND (`async_job`.`job_status` = 0)))); + `cloud`.`storage_pool` + LEFT JOIN `cloud`.`cluster` ON `storage_pool`.`cluster_id` = `cluster`.`id` + LEFT JOIN `cloud`.`data_center` ON `storage_pool`.`data_center_id` = `data_center`.`id` + LEFT JOIN `cloud`.`host_pod_ref` ON `storage_pool`.`pod_id` = `host_pod_ref`.`id` + LEFT JOIN `cloud`.`storage_pool_tags` ON `storage_pool_tags`.`pool_id` = `storage_pool`.`id` + LEFT JOIN `cloud`.`storage_pool_and_access_group_map` ON `storage_pool_and_access_group_map`.`pool_id` = `storage_pool`.`id` + LEFT JOIN `cloud`.`op_host_capacity` + ON `storage_pool`.`id` = `op_host_capacity`.`host_id` + AND `op_host_capacity`.`capacity_type` IN (3, 9) + LEFT JOIN `cloud`.`async_job` + ON `async_job`.`instance_id` = `storage_pool`.`id` + AND `async_job`.`instance_type` = 'StoragePool' + AND `async_job`.`job_status` = 0; diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.template_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.template_view.sql index 339e43860d88..76a8be16bda6 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.template_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.template_view.sql @@ -41,6 +41,7 @@ SELECT `vm_template`.`guest_os_id` AS `guest_os_id`, `guest_os`.`uuid` AS `guest_os_uuid`, `guest_os`.`display_name` AS `guest_os_name`, + `guest_os`.`category_id` AS `guest_os_category_id`, `vm_template`.`bootable` AS `bootable`, `vm_template`.`prepopulate` AS `prepopulate`, `vm_template`.`cross_zones` AS `cross_zones`, @@ -100,11 +101,15 @@ SELECT IFNULL(`data_center`.`id`, 0)) AS `temp_zone_pair`, `vm_template`.`direct_download` AS `direct_download`, `vm_template`.`deploy_as_is` AS `deploy_as_is`, + `vm_template`.`for_cks` AS `for_cks`, `user_data`.`id` AS `user_data_id`, `user_data`.`uuid` AS `user_data_uuid`, `user_data`.`name` AS `user_data_name`, `user_data`.`params` AS `user_data_params`, - `vm_template`.`user_data_link_policy` AS `user_data_policy` + `vm_template`.`user_data_link_policy` AS `user_data_policy`, + `extension`.`id` AS `extension_id`, + `extension`.`uuid` AS `extension_uuid`, + `extension`.`name` AS `extension_name` FROM (((((((((((((`vm_template` JOIN `guest_os` ON ((`guest_os`.`id` = `vm_template`.`guest_os_id`))) @@ -127,6 +132,7 @@ FROM OR (`template_zone_ref`.`zone_id` = `data_center`.`id`)))) LEFT JOIN `launch_permission` ON ((`launch_permission`.`template_id` = `vm_template`.`id`))) LEFT JOIN `user_data` ON ((`user_data`.`id` = `vm_template`.`user_data_id`)) + LEFT JOIN `extension` ON ((`extension`.`id` = `vm_template`.`extension_id`)) LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_template`.`id`) AND ((`resource_tags`.`resource_type` = 'Template') OR (`resource_tags`.`resource_type` = 'ISO'))))); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql index 340cfa9055fb..dcba71e10985 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_view.sql @@ -29,8 +29,6 @@ select user.lastname, user.email, user.state, - user.api_key, - user.secret_key, user.created, user.removed, user.timezone, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql index 430031b1d975..fbf126608eae 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.user_vm_view.sql @@ -56,6 +56,7 @@ SELECT `vm_instance`.`display_vm` AS `display_vm`, `vm_instance`.`delete_protection` AS `delete_protection`, `guest_os`.`uuid` AS `guest_os_uuid`, + `guest_os`.`display_name` AS `guest_os_display_name`, `vm_instance`.`pod_id` AS `pod_id`, `host_pod_ref`.`uuid` AS `pod_uuid`, `vm_instance`.`private_ip_address` AS `private_ip_address`, @@ -79,6 +80,7 @@ SELECT `vm_template`.`format` AS `template_format`, `vm_template`.`display_text` AS `template_display_text`, `vm_template`.`enable_password` AS `password_enabled`, + `vm_template`.`extension_id` AS `template_extension_id`, `iso`.`id` AS `iso_id`, `iso`.`uuid` AS `iso_uuid`, `iso`.`name` AS `iso_name`, @@ -103,6 +105,17 @@ SELECT `backup_offering`.`uuid` AS `backup_offering_uuid`, `backup_offering`.`id` AS `backup_offering_id`, `service_offering`.`name` AS `service_offering_name`, + `service_offering`.`vgpu_profile_id` AS `vgpu_profile_id`, + `vgpu_profile`.`uuid` AS `vgpu_profile_uuid`, + `vgpu_profile`.`name` AS `vgpu_profile_name`, + `vgpu_profile`.`video_ram` AS `vgpu_profile_video_ram`, + `vgpu_profile`.`max_heads` AS `vgpu_profile_max_heads`, + `vgpu_profile`.`max_resolution_x` AS `vgpu_profile_max_resolution_x`, + `vgpu_profile`.`max_resolution_y` AS `vgpu_profile_max_resolution_y`, + `gpu_card`.`id` AS `gpu_card_id`, + `gpu_card`.`uuid` AS `gpu_card_uuid`, + `gpu_card`.`name` AS `gpu_card_name`, + `service_offering`.`gpu_count` AS `gpu_count`, `disk_offering`.`name` AS `disk_offering_name`, `backup_offering`.`name` AS `backup_offering_name`, `storage_pool`.`id` AS `pool_id`, @@ -130,6 +143,8 @@ SELECT `nics`.`mac_address` AS `mac_address`, `nics`.`broadcast_uri` AS `broadcast_uri`, `nics`.`isolation_uri` AS `isolation_uri`, + `nics`.`enabled` AS `is_nic_enabled`, + `nic_details`.`value` AS `nic_dns_name`, `vpc`.`id` AS `vpc_id`, `vpc`.`uuid` AS `vpc_uuid`, `networks`.`uuid` AS `network_uuid`, @@ -169,9 +184,12 @@ SELECT `user_data`.`uuid` AS `user_data_uuid`, `user_data`.`name` AS `user_data_name`, `user_vm`.`user_data_details` AS `user_data_details`, - `vm_template`.`user_data_link_policy` AS `user_data_policy` + `vm_template`.`user_data_link_policy` AS `user_data_policy`, + `lease_expiry_date`.`value` AS `lease_expiry_date`, + `lease_expiry_action`.`value` AS `lease_expiry_action`, + `lease_action_execution`.`value` AS `lease_action_execution` FROM - (((((((((((((((((((((((((((((((((((`user_vm` + ((((((((((((((((((((((((((((((((((((((`user_vm` JOIN `vm_instance` ON (((`vm_instance`.`id` = `user_vm`.`id`) AND ISNULL(`vm_instance`.`removed`)))) JOIN `account` ON ((`vm_instance`.`account_id` = `account`.`id`))) @@ -189,6 +207,8 @@ FROM LEFT JOIN `service_offering` ON ((`vm_instance`.`service_offering_id` = `service_offering`.`id`))) LEFT JOIN `disk_offering` `svc_disk_offering` ON ((`volumes`.`disk_offering_id` = `svc_disk_offering`.`id`))) LEFT JOIN `disk_offering` ON ((`volumes`.`disk_offering_id` = `disk_offering`.`id`))) + LEFT JOIN `vgpu_profile` ON ((`service_offering`.`vgpu_profile_id` = `vgpu_profile`.`id`))) + LEFT JOIN `gpu_card` ON ((`vgpu_profile`.`card_id` = `gpu_card`.`id`))) LEFT JOIN `backup_offering` ON ((`vm_instance`.`backup_offering_id` = `backup_offering`.`id`))) LEFT JOIN `storage_pool` ON ((`volumes`.`pool_id` = `storage_pool`.`id`))) LEFT JOIN `security_group_vm_map` ON ((`vm_instance`.`id` = `security_group_vm_map`.`instance_id`))) @@ -196,11 +216,12 @@ FROM LEFT JOIN `user_data` ON ((`user_data`.`id` = `user_vm`.`user_data_id`))) LEFT JOIN `nics` ON (((`vm_instance`.`id` = `nics`.`instance_id`) AND ISNULL(`nics`.`removed`)))) + LEFT JOIN `nic_details` ON ((`nic_details`.`nic_id` = `nics`.`id`) AND (`nic_details`.`name` = 'nicdnsname'))) LEFT JOIN `networks` ON ((`nics`.`network_id` = `networks`.`id`))) LEFT JOIN `vpc` ON (((`networks`.`vpc_id` = `vpc`.`id`) AND ISNULL(`vpc`.`removed`)))) LEFT JOIN `user_ip_address` FORCE INDEX(`fk_user_ip_address__vm_id`) ON ((`user_ip_address`.`vm_id` = `vm_instance`.`id`))) - LEFT JOIN `user_vm_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`) + LEFT JOIN `vm_instance_details` `ssh_details` ON (((`ssh_details`.`vm_id` = `vm_instance`.`id`) AND (`ssh_details`.`name` = 'SSH.KeyPairNames')))) LEFT JOIN `resource_tags` ON (((`resource_tags`.`resource_id` = `vm_instance`.`id`) AND (`resource_tags`.`resource_type` = 'UserVm')))) @@ -211,9 +232,15 @@ FROM LEFT JOIN `affinity_group` ON ((`affinity_group_vm_map`.`affinity_group_id` = `affinity_group`.`id`))) LEFT JOIN `autoscale_vmgroup_vm_map` ON ((`autoscale_vmgroup_vm_map`.`instance_id` = `vm_instance`.`id`))) LEFT JOIN `autoscale_vmgroups` ON ((`autoscale_vmgroup_vm_map`.`vmgroup_id` = `autoscale_vmgroups`.`id`))) - LEFT JOIN `user_vm_details` `custom_cpu` ON (((`custom_cpu`.`vm_id` = `vm_instance`.`id`) + LEFT JOIN `vm_instance_details` `custom_cpu` ON (((`custom_cpu`.`vm_id` = `vm_instance`.`id`) AND (`custom_cpu`.`name` = 'CpuNumber')))) - LEFT JOIN `user_vm_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`) + LEFT JOIN `vm_instance_details` `custom_speed` ON (((`custom_speed`.`vm_id` = `vm_instance`.`id`) AND (`custom_speed`.`name` = 'CpuSpeed')))) - LEFT JOIN `user_vm_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`) - AND (`custom_ram_size`.`name` = 'memory')))); + LEFT JOIN `vm_instance_details` `custom_ram_size` ON (((`custom_ram_size`.`vm_id` = `vm_instance`.`id`) + AND (`custom_ram_size`.`name` = 'memory'))) + LEFT JOIN `vm_instance_details` `lease_expiry_date` ON ((`lease_expiry_date`.`vm_id` = `vm_instance`.`id`) + AND (`lease_expiry_date`.`name` = 'leaseexpirydate')) + LEFT JOIN `vm_instance_details` `lease_action_execution` ON ((`lease_action_execution`.`vm_id` = `vm_instance`.`id`) + AND (`lease_action_execution`.`name` = 'leaseactionexecution')) + LEFT JOIN `vm_instance_details` `lease_expiry_action` ON (((`lease_expiry_action`.`vm_id` = `vm_instance`.`id`) + AND (`lease_expiry_action`.`name` = 'leaseexpiryaction')))); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql index ffeb93e8fa7a..8ba7e5c6df77 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.volume_view.sql @@ -40,6 +40,10 @@ SELECT `volumes`.`chain_info` AS `chain_info`, `volumes`.`external_uuid` AS `external_uuid`, `volumes`.`encrypt_format` AS `encrypt_format`, + `volumes`.`kms_key_id` AS `kms_key_id`, + `kms_keys`.`uuid` AS `kms_key_uuid`, + `kms_keys`.`name` AS `kms_key_name`, + `volumes`.`kms_wrapped_key_id` AS `kms_wrapped_key_id`, `volumes`.`delete_protection` AS `delete_protection`, `account`.`id` AS `account_id`, `account`.`uuid` AS `account_uuid`, @@ -116,7 +120,7 @@ SELECT `resource_tag_domain`.`uuid` AS `tag_domain_uuid`, `resource_tag_domain`.`name` AS `tag_domain_name` FROM - ((((((((((((((((((`volumes` + (((((((((((((((((((`volumes` JOIN `account`ON ((`volumes`.`account_id` = `account`.`id`))) JOIN `domain`ON @@ -129,8 +133,10 @@ LEFT JOIN `vm_instance`ON ((`volumes`.`instance_id` = `vm_instance`.`id`))) LEFT JOIN `user_vm`ON ((`user_vm`.`id` = `vm_instance`.`id`))) -LEFT JOIN `volume_store_ref`ON +LEFT JOIN `volume_store_ref` ON ((`volumes`.`id` = `volume_store_ref`.`volume_id`))) +LEFT JOIN `kms_keys` ON + ((`volumes`.`kms_key_id` = `kms_keys`.`id`))) LEFT JOIN `service_offering`ON ((`vm_instance`.`service_offering_id` = `service_offering`.`id`))) LEFT JOIN `disk_offering`ON diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql index c74d50590de5..3669bb10122b 100644 --- a/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud.vpc_offering_view.sql @@ -28,7 +28,6 @@ select `vpc_offerings`.`display_text` AS `display_text`, `vpc_offerings`.`state` AS `state`, `vpc_offerings`.`default` AS `default`, - `vpc_offerings`.`for_nsx` AS `for_nsx`, `vpc_offerings`.`network_mode` AS `network_mode`, `vpc_offerings`.`created` AS `created`, `vpc_offerings`.`removed` AS `removed`, @@ -39,6 +38,7 @@ select `vpc_offerings`.`sort_key` AS `sort_key`, `vpc_offerings`.`routing_mode` AS `routing_mode`, `vpc_offerings`.`specify_as_number` AS `specify_as_number`, + `vpc_offerings`.`conserve_mode` AS `conserve_mode`, group_concat(distinct `domain`.`id` separator ',') AS `domain_id`, group_concat(distinct `domain`.`uuid` separator ',') AS `domain_uuid`, group_concat(distinct `domain`.`name` separator ',') AS `domain_name`, diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql new file mode 100644 index 000000000000..0646c3b6f919 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_summary_view.sql @@ -0,0 +1,48 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- cloud_usage.quota_summary_view source + +-- Create view for quota summary +DROP VIEW IF EXISTS `cloud_usage`.`quota_summary_view`; +CREATE VIEW `cloud_usage`.`quota_summary_view` AS +SELECT + cloud_usage.quota_account.account_id AS account_id, + cloud_usage.quota_account.quota_balance AS quota_balance, + cloud_usage.quota_account.quota_balance_date AS quota_balance_date, + cloud_usage.quota_account.quota_enforce AS quota_enforce, + cloud_usage.quota_account.quota_min_balance AS quota_min_balance, + cloud_usage.quota_account.quota_alert_date AS quota_alert_date, + cloud_usage.quota_account.quota_alert_type AS quota_alert_type, + cloud_usage.quota_account.last_statement_date AS last_statement_date, + cloud.account.uuid AS account_uuid, + cloud.account.account_name AS account_name, + cloud.account.state AS account_state, + cloud.account.removed AS account_removed, + cloud.domain.id AS domain_id, + cloud.domain.uuid AS domain_uuid, + cloud.domain.name AS domain_name, + cloud.domain.path AS domain_path, + cloud.domain.removed AS domain_removed, + cloud.projects.uuid AS project_uuid, + cloud.projects.name AS project_name, + cloud.projects.removed AS project_removed +FROM + cloud_usage.quota_account + INNER JOIN cloud.account ON (cloud.account.id = cloud_usage.quota_account.account_id) + INNER JOIN cloud.domain ON (cloud.domain.id = cloud.account.domain_id) + LEFT JOIN cloud.projects ON (cloud.account.type = 5 AND cloud.account.id = cloud.projects.project_account_id); diff --git a/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_usage_view.sql b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_usage_view.sql new file mode 100644 index 000000000000..7ac001384e8e --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/cloud_usage.quota_usage_view.sql @@ -0,0 +1,35 @@ +-- Licensed to the Apache Software Foundation (ASF) under one +-- or more contributor license agreements. See the NOTICE file +-- distributed with this work for additional information +-- regarding copyright ownership. The ASF licenses this file +-- to you under the Apache License, Version 2.0 (the +-- "License"); you may not use this file except in compliance +-- with the License. You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, +-- software distributed under the License is distributed on an +-- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +-- KIND, either express or implied. See the License for the +-- specific language governing permissions and limitations +-- under the License. + +-- VIEW `cloud_usage`.`quota_usage_view`; + +DROP VIEW IF EXISTS `cloud_usage`.`quota_usage_view`; +CREATE VIEW `cloud_usage`.`quota_usage_view` AS +SELECT qu.id, + qu.usage_item_id, + qu.zone_id, + qu.account_id, + qu.domain_id, + qu.usage_type, + qu.quota_used, + qu.start_date, + qu.end_date, + cu.usage_id AS resource_id, + cu.network_id as network_id, + cu.offering_id as offering_id +FROM `cloud_usage`.`quota_usage` qu +INNER JOIN `cloud_usage`.`cloud_usage` cu ON (cu.id = qu.usage_item_id); diff --git a/engine/schema/src/main/resources/META-INF/db/views/nic_dns_view.sql b/engine/schema/src/main/resources/META-INF/db/views/nic_dns_view.sql new file mode 100644 index 000000000000..8c7737db0884 --- /dev/null +++ b/engine/schema/src/main/resources/META-INF/db/views/nic_dns_view.sql @@ -0,0 +1,40 @@ +-- 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. + +-- VIEW `cloud`.`nic_dns_view`; + +DROP VIEW IF EXISTS `cloud`.`nic_dns_view`; +CREATE VIEW `cloud`.`nic_dns_view` AS +SELECT + n.id AS id, + n.uuid AS uuid, + n.instance_id AS instance_id, + n.network_id AS network_id, + n.ip4_address AS ip4_address, + n.ip6_address AS ip6_address, + n.removed AS removed, + nd.value AS nic_dns_name, + map.dns_zone_id AS dns_zone_id, + map.sub_domain AS sub_domain +FROM + `cloud`.`nics` n + INNER JOIN + `cloud`.`dns_zone_network_map` map ON n.network_id = map.network_id + LEFT JOIN + `cloud`.`nic_details` nd ON n.id = nd.nic_id AND nd.name = 'nicdnsname' +WHERE + n.instance_id IS NOT NULL AND map.removed IS NULL; diff --git a/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java b/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java index 76c1092546a6..f9528d5d57ff 100644 --- a/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/capacity/dao/CapacityDaoImplTest.java @@ -16,40 +16,67 @@ // under the License. package com.cloud.capacity.dao; +import com.cloud.capacity.CapacityVO; +import com.cloud.host.Host; +import com.cloud.utils.Pair; +import com.cloud.utils.Ternary; +import com.cloud.utils.db.SearchBuilder; +import com.cloud.utils.db.SearchCriteria; +import com.cloud.utils.db.TransactionLegacy; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; + import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.any; +import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.mockito.InjectMocks; -import org.mockito.Mockito; -import org.mockito.Spy; -import org.mockito.junit.MockitoJUnitRunner; - -import com.cloud.capacity.CapacityVO; -import com.cloud.utils.db.SearchBuilder; -import com.cloud.utils.db.SearchCriteria; - @RunWith(MockitoJUnitRunner.class) public class CapacityDaoImplTest { @Spy @InjectMocks CapacityDaoImpl capacityDao = new CapacityDaoImpl(); + @Mock + private CapacityVO mockEntity; + + @Mock + private TransactionLegacy txn; + @Mock + private PreparedStatement pstmt; + @Mock + private ResultSet resultSet; + private MockedStatic mockedTransactionLegacy; + private SearchBuilder searchBuilder; private SearchCriteria searchCriteria; + private List capacityTypes; + private List expectedCapacities; @Before public void setUp() { @@ -59,6 +86,27 @@ public void setUp() { searchCriteria = mock(SearchCriteria.class); doReturn(searchBuilder).when(capacityDao).createSearchBuilder(); when(searchBuilder.create()).thenReturn(searchCriteria); + + mockedTransactionLegacy = Mockito.mockStatic(TransactionLegacy.class); + mockedTransactionLegacy.when(TransactionLegacy::currentTxn).thenReturn(txn); + + // Setup common test data + capacityTypes = Arrays.asList((short) 1, (short) 2, (short) 3); + expectedCapacities = Arrays.asList(mock(CapacityVO.class), mock(CapacityVO.class)); + doReturn(expectedCapacities).when(capacityDao).listBy(searchCriteria); + } + + private CapacityVO createMockCapacityVO(Long id) { + CapacityVO capacity = mock(CapacityVO.class); + when(capacity.getId()).thenReturn(id); + return capacity; + } + + @After + public void tearDown() { + if (mockedTransactionLegacy != null) { + mockedTransactionLegacy.close(); + } } @Test @@ -96,4 +144,410 @@ public void testListByHostIdTypesEmptyResult() { verify(capacityDao).listBy(searchCriteria); assertTrue(result.isEmpty()); } + + @Test + public void testListClustersCrossingThresholdEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + List result = capacityDao.listClustersCrossingThreshold((short)1, 1L, "cpu.threshold", 5000L); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testFindCapacityByZoneAndHostTagNoResults() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + Ternary result = capacityDao.findCapacityByZoneAndHostTag(1L, "host-tag"); + assertNotNull(result); + assertEquals(Long.valueOf(0L), result.first()); + assertEquals(Long.valueOf(0L), result.second()); + assertEquals(Long.valueOf(0L), result.third()); + } + @Test + public void testFindByHostIdType() { + CapacityVO capacity = new CapacityVO(); + capacity.setHostId(1L); + capacity.setCapacityType((short) 1); + + doReturn(capacity).when(capacityDao).findOneBy(any()); + + CapacityVO found = capacityDao.findByHostIdType(1L, (short) 1); + assertNotNull(found); + assertEquals(Long.valueOf(1L), found.getHostOrPoolId()); + } + + @Test + public void testUpdateAllocatedAddition() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + doNothing().when(txn).start(); + when(txn.commit()).thenReturn(true); + + capacityDao.updateAllocated(1L, 1000L, (short)1, true); + + verify(txn, times(1)).start(); + verify(txn, times(1)).commit(); + verify(pstmt, times(1)).executeUpdate(); + } + + @Test + public void testUpdateAllocatedSubtraction() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + doNothing().when(txn).start(); + when(txn.commit()).thenReturn(true); + + capacityDao.updateAllocated(1L, 500L, (short)1, false); + + verify(txn, times(1)).start(); + verify(txn, times(1)).commit(); + verify(pstmt, times(1)).executeUpdate(); + } + + @Test + public void testFindFilteredCapacityByEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + List result = capacityDao.findFilteredCapacityBy(null, null, null, null, Collections.emptyList(), Collections.emptyList()); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testListClustersInZoneOrPodByHostCapacitiesEmpty() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + List resultZone = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, true); + assertNotNull(resultZone); + assertTrue(resultZone.isEmpty()); + + List resultPod = capacityDao.listClustersInZoneOrPodByHostCapacities(1L, 123L, 2, 2048L, false); + assertNotNull(resultPod); + assertTrue(resultPod.isEmpty()); + } + + + @Test + public void testListHostsWithEnoughCapacityEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + List result = capacityDao.listHostsWithEnoughCapacity(1, 100L, 200L, Host.Type.Routing.toString()); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + + @Test + public void testOrderClustersByAggregateCapacityEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + Pair, Map> result = capacityDao.orderClustersByAggregateCapacity(1L, 1L, (short) 1, true); + assertNotNull(result); + assertTrue(result.first().isEmpty()); + assertTrue(result.second().isEmpty()); + } + + + @Test + public void testOrderPodsByAggregateCapacityEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + Pair, Map> result = capacityDao.orderPodsByAggregateCapacity(1L, (short) 1); + assertNotNull(result); + assertTrue(result.first().isEmpty()); + assertTrue(result.second().isEmpty()); + } + + + @Test + public void testUpdateCapacityState() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeUpdate()).thenReturn(1); + + capacityDao.updateCapacityState(1L, 1L, 1L, 1L, "Enabled", new short[]{1}); + + verify(pstmt, times(1)).executeUpdate(); + } + + + @Test + public void testFindClusterConsumption() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(true); + when(resultSet.getFloat(1)).thenReturn(0.5f); + + float result = capacityDao.findClusterConsumption(1L, (short) 1, 1000L); + assertEquals(0.5f, result, 0.0f); + } + + @Test + public void testListPodsByHostCapacitiesEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + List result = capacityDao.listPodsByHostCapacities(1L, 2, 1024L); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testOrderHostsByFreeCapacityEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + Pair, Map> result = capacityDao.orderHostsByFreeCapacity(1L, 1L, (short) 0); + assertNotNull(result); + assertTrue(result.first().isEmpty()); + } + + @Test + public void testFindByClusterPodZoneEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + List result = capacityDao.findByClusterPodZone(1L, 1L, 1L); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testListCapacitiesGroupedByLevelAndTypeEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + List result = capacityDao.listCapacitiesGroupedByLevelAndType(0, 1L, + 1L, 1L, 0, Collections.emptyList(), Collections.emptyList(), 1L); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testFindCapacityByEmptyResult() throws Exception { + when(txn.prepareAutoCloseStatement(anyString())).thenReturn(pstmt); + when(pstmt.executeQuery()).thenReturn(resultSet); + when(resultSet.next()).thenReturn(false); + + List result = capacityDao.findCapacityBy(1, 1L, 1L, 1L); + assertNotNull(result); + assertTrue(result.isEmpty()); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithAllParameters() { + // Given + Long zoneId = 100L; + Long clusterId = 200L; + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, capacityTypes); + + // Then + verify(searchBuilder).and("zoneId", mockEntity.getDataCenterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("clusterId", mockEntity.getClusterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("capacityTypes", mockEntity.getCapacityType(), SearchCriteria.Op.IN); + verify(searchBuilder).and("capacityState", mockEntity.getCapacityState(), SearchCriteria.Op.EQ); + + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria).setParameters("clusterId", clusterId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + verify(capacityDao).listBy(searchCriteria); + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithNullZoneId() { + // Given + Long clusterId = 200L; + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(null, clusterId, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, Mockito.times(0)).setParameters(eq("zoneId"), any()); + verify(searchCriteria).setParameters("clusterId", clusterId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithNullClusterId() { + // Given + Long zoneId = 100L; + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(zoneId, null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria, never()).setParameters(eq("clusterId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListHostCapacityByCapacityTypes_WithEmptyCapacityTypes() { + // Given + Long zoneId = 100L; + Long clusterId = 200L; + List emptyCapacityTypes = Collections.emptyList(); + + // When + List result = capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, emptyCapacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityTypes", emptyCapacityTypes.toArray()); + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListPodCapacityByCapacityTypes_WithAllParameters() { + // Given + Long zoneId = 100L; + + // When + List result = capacityDao.listPodCapacityByCapacityTypes(zoneId, capacityTypes); + + // Then + verify(searchBuilder).and("zoneId", mockEntity.getDataCenterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("capacityTypes", mockEntity.getCapacityType(), SearchCriteria.Op.IN); + verify(searchBuilder).and("capacityState", mockEntity.getCapacityState(), SearchCriteria.Op.EQ); + + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListPodCapacityByCapacityTypes_WithNullZoneId() { + // When + List result = capacityDao.listPodCapacityByCapacityTypes(null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, never()).setParameters(eq("zoneId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithAllParameters() { + // Given + Long zoneId = 100L; + Long podId = 300L; + + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, capacityTypes); + + // Then + verify(searchBuilder).and("zoneId", mockEntity.getDataCenterId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("podId", mockEntity.getPodId(), SearchCriteria.Op.EQ); + verify(searchBuilder).and("capacityTypes", mockEntity.getCapacityType(), SearchCriteria.Op.IN); + verify(searchBuilder).and("capacityState", mockEntity.getCapacityState(), SearchCriteria.Op.EQ); + + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria).setParameters("podId", podId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithNullZoneId() { + // Given + Long podId = 300L; + + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(null, podId, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, never()).setParameters(eq("zoneId"), any()); + verify(searchCriteria).setParameters("podId", podId); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithNullPodId() { + // Given + Long zoneId = 100L; + + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(zoneId, null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria).setParameters("zoneId", zoneId); + verify(searchCriteria, never()).setParameters(eq("podId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testListClusterCapacityByCapacityTypes_WithBothIdsNull() { + // When + List result = capacityDao.listClusterCapacityByCapacityTypes(null, null, capacityTypes); + + // Then + verify(searchCriteria).setParameters("capacityState", "Enabled"); + verify(searchCriteria, never()).setParameters(eq("zoneId"), any()); + verify(searchCriteria, never()).setParameters(eq("podId"), any()); + verify(searchCriteria).setParameters("capacityTypes", capacityTypes.toArray()); + + assertEquals("Should return expected capacities", expectedCapacities, result); + } + + @Test + public void testAllMethods_VerifySearchBuilderSetup() { + // Test that all methods properly set up the search builder + Long zoneId = 100L; + Long clusterId = 200L; + Long podId = 300L; + + // Test host capacity method + capacityDao.listHostCapacityByCapacityTypes(zoneId, clusterId, capacityTypes); + + // Test pod capacity method + capacityDao.listPodCapacityByCapacityTypes(zoneId, capacityTypes); + + // Test cluster capacity method + capacityDao.listClusterCapacityByCapacityTypes(zoneId, podId, capacityTypes); + + // Verify createSearchBuilder was called 3 times + verify(capacityDao, times(3)).createSearchBuilder(); + + // Verify done() was called 3 times + verify(searchBuilder, times(3)).done(); + + // Verify listBy was called 3 times + verify(capacityDao, times(3)).listBy(searchCriteria); + } } diff --git a/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java b/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java index f8673fa9c9fd..c1a558a72b33 100644 --- a/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/dc/dao/ClusterDaoImplTest.java @@ -92,7 +92,7 @@ public void listDistinctHypervisorsArchAcrossClusters_WithZone() { when(cluster2.getArch()).thenReturn(CPU.CPUArch.arm64); List dummyHosts = Arrays.asList(cluster1, cluster2); doReturn(dummyHosts).when(clusterDao).search(any(SearchCriteria.class), isNull()); - List> result = clusterDao.listDistinctHypervisorsArchAcrossClusters(zoneId); + List> result = clusterDao.listDistinctHypervisorsAndArchExcludingExternalType(zoneId); assertNotNull(result); assertEquals(2, result.size()); assertEquals(Hypervisor.HypervisorType.XenServer, result.get(0).first()); @@ -109,7 +109,7 @@ public void listDistinctHypervisorsArchAcrossClusters_WithoutZone() { when(cluster.getArch()).thenReturn(CPU.CPUArch.amd64); List dummyHosts = Collections.singletonList(cluster); doReturn(dummyHosts).when(clusterDao).search(any(SearchCriteria.class), isNull()); - List> result = clusterDao.listDistinctHypervisorsArchAcrossClusters(zoneId); + List> result = clusterDao.listDistinctHypervisorsAndArchExcludingExternalType(zoneId); assertNotNull(result); assertEquals(1, result.size()); assertEquals(Hypervisor.HypervisorType.VMware, result.get(0).first()); diff --git a/engine/schema/src/test/java/com/cloud/gpu/dao/GpuCardDaoImplTest.java b/engine/schema/src/test/java/com/cloud/gpu/dao/GpuCardDaoImplTest.java new file mode 100644 index 000000000000..e0a283add992 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/gpu/dao/GpuCardDaoImplTest.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.cloud.gpu.dao; + +import com.cloud.gpu.GpuCardVO; +import com.cloud.utils.db.SearchCriteria; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class GpuCardDaoImplTest { + + @Spy + @InjectMocks + GpuCardDaoImpl gpuCardDaoImpl = new GpuCardDaoImpl(); + + @Test + public void findByVendorIdAndDeviceId() { + doReturn(mock(GpuCardVO.class)).when(gpuCardDaoImpl).findOneBy(any(SearchCriteria.class)); + + GpuCardVO gpuCard = gpuCardDaoImpl.findByVendorIdAndDeviceId("0d1a", "1a3b"); + Assert.assertNotNull("Expected non-null gpu card", gpuCard); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuCardDaoImpl).findOneBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", + "gpu_card.vendor_id = ? AND gpu_card.device_id = ?", + scCaptor.getValue().getWhereClause().trim()); + } +} diff --git a/engine/schema/src/test/java/com/cloud/gpu/dao/GpuDeviceDaoImplTest.java b/engine/schema/src/test/java/com/cloud/gpu/dao/GpuDeviceDaoImplTest.java new file mode 100644 index 000000000000..1780fbd3df38 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/gpu/dao/GpuDeviceDaoImplTest.java @@ -0,0 +1,277 @@ +/* + * 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.gpu.dao; + +import com.cloud.gpu.GpuDeviceVO; +import com.cloud.utils.db.Filter; +import com.cloud.utils.db.SearchCriteria; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.Collections; +import java.util.List; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class GpuDeviceDaoImplTest { + + @Spy + @InjectMocks + GpuDeviceDaoImpl gpuDeviceDao = new GpuDeviceDaoImpl(); + + @Before + public void setUp() throws Exception { + } + + @After + public void tearDown() throws Exception { + } + + @Test + public void listByIds_emptyList() { + List devices = gpuDeviceDao.listByIds(null); + Assert.assertTrue("Expected empty list", devices.isEmpty()); + devices = gpuDeviceDao.listByIds(Collections.emptyList()); + Assert.assertTrue("Expected empty list", devices.isEmpty()); + } + + @Test + public void listByIds() { + doReturn(List.of(mock(GpuDeviceVO.class))).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List devices = gpuDeviceDao.listByIds(List.of(1L, 2L, 3L)); + + Assert.assertFalse("Expected non empty list", devices.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + SearchCriteria sc = scCaptor.getValue(); + Assert.assertEquals("Expected correct where clause", "gpu_device.id IN (?,?,?)", sc.getWhereClause().trim()); + } + + @Test + public void findByHostIdAndBusAddress() { + doReturn(mock(GpuDeviceVO.class)).when(gpuDeviceDao).findOneBy(any(SearchCriteria.class)); + + GpuDeviceVO device = gpuDeviceDao.findByHostIdAndBusAddress(1L, "0000:00:1f.6"); + + Assert.assertNotNull("Expected non-null device", device); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).findOneBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "gpu_device.host_id = ? AND gpu_device.bus_address = ?", + scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void listByHostId() { + doReturn(List.of(mock(GpuDeviceVO.class))).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List devices = gpuDeviceDao.listByHostId(1L); + + Assert.assertFalse("Expected non empty list", devices.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "gpu_device.host_id = ?", + scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void listByVmId() { + doReturn(List.of(mock(GpuDeviceVO.class))).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List devices = gpuDeviceDao.listByVmId(1L); + + Assert.assertFalse("Expected non empty list", devices.isEmpty()); + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + + Assert.assertEquals("Expected correct where clause", "gpu_device.vm_id = ?", + scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void isVgpuProfileInUse() { + doReturn(1).when(gpuDeviceDao).getCount(any(SearchCriteria.class)); + + boolean vgpuProfileInUse = gpuDeviceDao.isVgpuProfileInUse(1L); + + Assert.assertTrue("Expected vGPU profile to be in use", vgpuProfileInUse); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).getCount(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "gpu_device.vgpu_profile_id = ?", + scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void isGpuCardInUse() { + doReturn(1).when(gpuDeviceDao).getCount(any(SearchCriteria.class)); + + boolean vgpuProfileInUse = gpuDeviceDao.isGpuCardInUse(1L); + + Assert.assertTrue("Expected GPU Card to be in use", vgpuProfileInUse); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).getCount(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "gpu_device.card_id = ?", + scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void listByHostAndVm() { + doReturn(List.of(mock(GpuDeviceVO.class))).when(gpuDeviceDao).search(any(SearchCriteria.class), any()); + + List devices = gpuDeviceDao.listByHostAndVm(1L, 2L); + + Assert.assertFalse("Expected non empty list", devices.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + ArgumentCaptor filterCaptor = ArgumentCaptor.forClass(Filter.class); + verify(gpuDeviceDao).search(scCaptor.capture(), filterCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "gpu_device.host_id = ? AND gpu_device.vm_id = ?", + scCaptor.getValue().getWhereClause().trim()); + Assert.assertNull("Expected no filter", filterCaptor.getValue()); + } + + @Test + public void listDevicesForAllocation() { + doReturn(List.of(mock(GpuDeviceVO.class))).when(gpuDeviceDao).search(any(SearchCriteria.class), any()); + + List devices = gpuDeviceDao.listDevicesForAllocation(1L, 2L); + + Assert.assertFalse("Expected non empty list", devices.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + ArgumentCaptor filterCaptor = ArgumentCaptor.forClass(Filter.class); + verify(gpuDeviceDao).search(scCaptor.capture(), filterCaptor.capture()); + Assert.assertEquals("Expected correct where clause", + "gpu_device.host_id = ? AND gpu_device.vgpu_profile_id=? AND gpu_device.state = ? AND gpu_device" + + ".managed_state = ? AND gpu_device.type != ?", + scCaptor.getValue().getWhereClause().trim()); + Assert.assertNull("Expected no filter", filterCaptor.getValue()); + } + + @Test + public void searchAndCountGpuDevices() { + } + + @Test + public void getDistinctGpuCardIds_no_devices() { + doReturn(null).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List cardIds = gpuDeviceDao.getDistinctGpuCardIds(); + + Assert.assertTrue("Expected empty list", cardIds.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "", scCaptor.getValue().getWhereClause().trim()); + } + + + @Test + public void getDistinctGpuCardIds() { + GpuDeviceVO device1 = mock(GpuDeviceVO.class); + GpuDeviceVO device2 = mock(GpuDeviceVO.class); + GpuDeviceVO device3 = mock(GpuDeviceVO.class); + when(device1.getCardId()).thenReturn(1L); + when(device2.getCardId()).thenReturn(2L); + when(device3.getCardId()).thenReturn(1L); + + doReturn(List.of(device1, device2, device3)).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List cardIds = gpuDeviceDao.getDistinctGpuCardIds(); + + Assert.assertEquals("Expected 2 card IDs", 2, cardIds.size()); + + Assert.assertTrue("Expected card ID 1 in list", cardIds.contains(1L)); + Assert.assertTrue("Expected card ID 2 in list", cardIds.contains(2L)); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "", scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void getDistinctVgpuProfileIds_no_devices() { + doReturn(null).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List cardIds = gpuDeviceDao.getDistinctVgpuProfileIds(); + + Assert.assertTrue("Expected empty list", cardIds.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "", scCaptor.getValue().getWhereClause().trim()); + } + + + @Test + public void getDistinctVgpuProfileIds() { + GpuDeviceVO device1 = mock(GpuDeviceVO.class); + GpuDeviceVO device2 = mock(GpuDeviceVO.class); + GpuDeviceVO device3 = mock(GpuDeviceVO.class); + when(device1.getVgpuProfileId()).thenReturn(1L); + when(device2.getVgpuProfileId()).thenReturn(2L); + when(device3.getVgpuProfileId()).thenReturn(1L); + + doReturn(List.of(device1, device2, device3)).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List cardIds = gpuDeviceDao.getDistinctVgpuProfileIds(); + + Assert.assertEquals("Expected 2 VgpuProfile IDs", 2, cardIds.size()); + + Assert.assertTrue("Expected VgpuProfile ID 1 in list", cardIds.contains(1L)); + Assert.assertTrue("Expected VgpuProfile ID 2 in list", cardIds.contains(2L)); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "", scCaptor.getValue().getWhereClause().trim()); + } + + + @Test + public void listByParentGpuDeviceId() { + doReturn(List.of(mock(GpuDeviceVO.class))).when(gpuDeviceDao).listBy(any(SearchCriteria.class)); + + List devices = gpuDeviceDao.listByParentGpuDeviceId(1L); + + Assert.assertFalse("Expected non empty list", devices.isEmpty()); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(gpuDeviceDao).listBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "gpu_device.parent_gpu_device_id = ?", + scCaptor.getValue().getWhereClause().trim()); + } +} diff --git a/engine/schema/src/test/java/com/cloud/gpu/dao/VgpuProfileDaoImplTest.java b/engine/schema/src/test/java/com/cloud/gpu/dao/VgpuProfileDaoImplTest.java new file mode 100644 index 000000000000..cd7199d020f3 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/gpu/dao/VgpuProfileDaoImplTest.java @@ -0,0 +1,70 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package com.cloud.gpu.dao; + +import com.cloud.gpu.VgpuProfileVO; +import com.cloud.utils.db.SearchCriteria; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class VgpuProfileDaoImplTest { + + @Spy + @InjectMocks + VgpuProfileDaoImpl vgpuProfileDaoImpl = new VgpuProfileDaoImpl(); + + @Test + public void findByNameAndCardId() { + doReturn(mock(VgpuProfileVO.class)).when(vgpuProfileDaoImpl).findOneBy(any(SearchCriteria.class)); + + VgpuProfileVO vgpuProfile = vgpuProfileDaoImpl.findByNameAndCardId("test-profile", 1L); + Assert.assertNotNull("Expected non-null vgpu profile", vgpuProfile); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(vgpuProfileDaoImpl).findOneBy(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", + "vgpu_profile.name = ? AND vgpu_profile.card_id=?", + scCaptor.getValue().getWhereClause().trim()); + } + + @Test + public void removeByCardId() { + doReturn(1).when(vgpuProfileDaoImpl).remove(any(SearchCriteria.class)); + + int removed = vgpuProfileDaoImpl.removeByCardId(123L); + Assert.assertEquals("Expected one vgpu profile removed", 1, removed); + + ArgumentCaptor scCaptor = ArgumentCaptor.forClass(SearchCriteria.class); + verify(vgpuProfileDaoImpl).remove(scCaptor.capture()); + Assert.assertEquals("Expected correct where clause", "vgpu_profile.card_id=?", + scCaptor.getValue().getWhereClause().trim()); + } +} diff --git a/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java b/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java index ad37ef388305..c76eedb01e93 100644 --- a/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/host/dao/HostDaoImplTest.java @@ -116,6 +116,7 @@ public void testIsHostUp() { public void testFindHostIdsByZoneClusterResourceStateTypeAndHypervisorType() { Long zoneId = 1L; Long clusterId = 2L; + Long msId = 1L; List resourceStates = List.of(ResourceState.Enabled); List types = List.of(Host.Type.Routing); List hypervisorTypes = List.of(Hypervisor.HypervisorType.KVM); @@ -129,10 +130,11 @@ public void testFindHostIdsByZoneClusterResourceStateTypeAndHypervisorType() { doReturn(sb).when(hostDao).createSearchBuilder(Long.class); doReturn(mockResults).when(hostDao).customSearch(any(SearchCriteria.class), any()); List hostIds = hostDao.findHostIdsByZoneClusterResourceStateTypeAndHypervisorType( - zoneId, clusterId, resourceStates, types, hypervisorTypes); + zoneId, clusterId, msId, resourceStates, types, hypervisorTypes); assertEquals(mockResults, hostIds); Mockito.verify(sc).setParameters("zoneId", zoneId); Mockito.verify(sc).setParameters("clusterId", clusterId); + Mockito.verify(sc).setParameters("msId", msId); Mockito.verify(sc).setParameters("resourceState", resourceStates.toArray()); Mockito.verify(sc).setParameters("type", types.toArray()); Mockito.verify(sc).setParameters("hypervisorTypes", hypervisorTypes.toArray()); diff --git a/engine/schema/src/test/java/com/cloud/network/dao/LoadBalancerVOTest.java b/engine/schema/src/test/java/com/cloud/network/dao/LoadBalancerVOTest.java new file mode 100644 index 000000000000..8f84ecac3dda --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/network/dao/LoadBalancerVOTest.java @@ -0,0 +1,45 @@ +// 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.network.dao; + +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +public class LoadBalancerVOTest { + @Test + public void testSetCidrList() { + LoadBalancerVO loadBalancer = new LoadBalancerVO(); + String cidrList = "192.168.1.0/24,10.0.0.0/16"; + loadBalancer.setCidrList(cidrList); + assertEquals(cidrList, loadBalancer.getCidrList()); + } + + @Test + public void testSetCidrListEmpty() { + LoadBalancerVO loadBalancer = new LoadBalancerVO(); + loadBalancer.setCidrList(""); + assertEquals("", loadBalancer.getCidrList()); + } + + @Test + public void testSetCidrListNull() { + LoadBalancerVO loadBalancer = new LoadBalancerVO(); + loadBalancer.setCidrList(null); + assertNull(loadBalancer.getCidrList()); + } +} diff --git a/engine/schema/src/test/java/com/cloud/network/dao/NetworkDaoImplTest.java b/engine/schema/src/test/java/com/cloud/network/dao/NetworkDaoImplTest.java index ab5f43521052..a78eab568af0 100644 --- a/engine/schema/src/test/java/com/cloud/network/dao/NetworkDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/network/dao/NetworkDaoImplTest.java @@ -22,7 +22,6 @@ import com.cloud.network.Networks; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; -import com.cloud.utils.db.TransactionLegacy; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -30,7 +29,6 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; - import java.util.List; @RunWith(MockitoJUnitRunner.class) @@ -46,26 +44,21 @@ public class NetworkDaoImplTest { List listNetworkVoMock; @Test - public void listByPhysicalNetworkTrafficTypeTestSetParametersValidation() throws Exception { + public void listByPhysicalNetworkTrafficTypeTestSetParametersValidation() { NetworkDaoImpl networkDaoImplSpy = Mockito.spy(NetworkDaoImpl.class); - TransactionLegacy txn = TransactionLegacy.open("runNetworkDaoImplTest"); - try { - networkDaoImplSpy.AllFieldsSearch = searchBuilderNetworkVoMock; - Mockito.doReturn(searchCriteriaNetworkVoMock).when(searchBuilderNetworkVoMock).create(); - Mockito.doNothing().when(searchCriteriaNetworkVoMock).setParameters(Mockito.anyString(), Mockito.any()); - Mockito.doReturn(listNetworkVoMock).when(networkDaoImplSpy).listBy(Mockito.any(SearchCriteria.class)); - - long expectedPhysicalNetwork = 2513l; + networkDaoImplSpy.AllFieldsSearch = searchBuilderNetworkVoMock; + Mockito.doReturn(searchCriteriaNetworkVoMock).when(searchBuilderNetworkVoMock).create(); + Mockito.doNothing().when(searchCriteriaNetworkVoMock).setParameters(Mockito.anyString(), Mockito.any()); + Mockito.doReturn(listNetworkVoMock).when(networkDaoImplSpy).listBy(Mockito.any(SearchCriteria.class)); - for (Networks.TrafficType trafficType : Networks.TrafficType.values()) { - List result = networkDaoImplSpy.listByPhysicalNetworkTrafficType(expectedPhysicalNetwork, trafficType); - Assert.assertEquals(listNetworkVoMock, result); - Mockito.verify(searchCriteriaNetworkVoMock).setParameters("trafficType", trafficType); - } + long expectedPhysicalNetwork = 2513l; - Mockito.verify(searchCriteriaNetworkVoMock, Mockito.times(Networks.TrafficType.values().length)).setParameters("physicalNetwork", expectedPhysicalNetwork); - } finally { - txn.close(); + for (Networks.TrafficType trafficType : Networks.TrafficType.values()) { + List result = networkDaoImplSpy.listByPhysicalNetworkTrafficType(expectedPhysicalNetwork, trafficType); + Assert.assertEquals(listNetworkVoMock, result); + Mockito.verify(searchCriteriaNetworkVoMock).setParameters("trafficType", trafficType); } + + Mockito.verify(searchCriteriaNetworkVoMock, Mockito.times(Networks.TrafficType.values().length)).setParameters("physicalNetworkId", expectedPhysicalNetwork); } } diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java index e97a887cc471..5cff77869be8 100644 --- a/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/storage/dao/VMTemplateDaoImplTest.java @@ -20,8 +20,13 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.util.ArrayList; @@ -45,8 +50,11 @@ import com.cloud.hypervisor.Hypervisor; import com.cloud.storage.Storage; import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VMTemplateZoneVO; import com.cloud.utils.Pair; import com.cloud.utils.db.Filter; +import com.cloud.utils.db.GenericSearchBuilder; +import com.cloud.utils.db.JoinBuilder; import com.cloud.utils.db.SearchBuilder; import com.cloud.utils.db.SearchCriteria; @@ -56,6 +64,9 @@ public class VMTemplateDaoImplTest { @Mock HostDao hostDao; + @Mock + VMTemplateZoneDao templateZoneDao; + @Spy @InjectMocks VMTemplateDaoImpl templateDao = new VMTemplateDaoImpl(); @@ -65,7 +76,8 @@ public void testFindLatestTemplateByName_ReturnsTemplate() { VMTemplateVO expectedTemplate = new VMTemplateVO(); List returnedList = Collections.singletonList(expectedTemplate); doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); - VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault()); + VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.KVM, + CPU.CPUArch.getDefault()); assertNotNull("Expected a non-null template", result); assertEquals("Expected the returned template to be the first element", expectedTemplate, result); } @@ -74,7 +86,8 @@ public void testFindLatestTemplateByName_ReturnsTemplate() { public void testFindLatestTemplateByName_ReturnsNullWhenNoTemplateFound() { List emptyList = Collections.emptyList(); doReturn(emptyList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); - VMTemplateVO result = templateDao.findLatestTemplateByName("test", CPU.CPUArch.getDefault()); + VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.VMware, + CPU.CPUArch.getDefault()); assertNull("Expected null when no templates are found", result); } @@ -83,7 +96,8 @@ public void testFindLatestTemplateByName_NullArch() { VMTemplateVO expectedTemplate = new VMTemplateVO(); List returnedList = Collections.singletonList(expectedTemplate); doReturn(returnedList).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); - VMTemplateVO result = templateDao.findLatestTemplateByName("test", null); + VMTemplateVO result = templateDao.findLatestTemplateByName("test", Hypervisor.HypervisorType.XenServer, + null); assertNotNull("Expected a non-null template even if arch is null", result); assertEquals("Expected the returned template to be the first element", expectedTemplate, result); } @@ -188,6 +202,125 @@ public void testFindLatestTemplateByTypeAndHypervisorAndArch_NotFound() { assertNull(result); } + private void mockTemplateZoneJoin() { + VMTemplateZoneVO templateZoneVO = mock(VMTemplateZoneVO.class); + SearchBuilder templateZoneVOSearchBuilder = mock(SearchBuilder.class); + when(templateZoneVOSearchBuilder.entity()).thenReturn(templateZoneVO); + when(templateZoneDao.createSearchBuilder()).thenReturn(templateZoneVOSearchBuilder); + } + + @Test + public void testListTemplateIsoByArchAndZone_WithDataCenterId() { + Long dataCenterId = 1L; + CPU.CPUArch arch = CPU.CPUArch.getDefault(); + Boolean isIso = true; + VMTemplateVO templateVO = mock(VMTemplateVO.class); + GenericSearchBuilder searchBuilder = mock(GenericSearchBuilder.class); + when(searchBuilder.entity()).thenReturn(templateVO); + SearchCriteriasearchCriteria = mock(SearchCriteria.class); + when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder); + when(searchBuilder.create()).thenReturn(searchCriteria); + mockTemplateZoneJoin(); + doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null); + List result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false); + assertNotNull(result); + verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId()); + verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, times(1)).and(eq("isIso"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, times(1)).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER)); + verify(templateDao, times(1)).customSearch(searchCriteria, null); + } + + @Test + public void testListTemplateIsoByArchAndZone_WithoutDataCenterId() { + Long dataCenterId = null; + CPU.CPUArch arch = CPU.CPUArch.getDefault(); + Boolean isIso = false; + VMTemplateVO templateVO = mock(VMTemplateVO.class); + GenericSearchBuilder searchBuilder = mock(GenericSearchBuilder.class); + when(searchBuilder.entity()).thenReturn(templateVO); + SearchCriteriasearchCriteria = mock(SearchCriteria.class); + when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder); + when(searchBuilder.create()).thenReturn(searchCriteria); + doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null); + List result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false); + assertNotNull(result); + verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId()); + verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, times(1)).and(eq("isIso"), any(), eq(SearchCriteria.Op.NEQ)); + verify(searchBuilder, never()).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER)); + verify(templateDao, times(1)).customSearch(searchCriteria, null); + } + + @Test + public void testListTemplateIsoByArchAndZone_WithoutArch() { + Long dataCenterId = 1L; + CPU.CPUArch arch = null; + Boolean isIso = true; + VMTemplateVO templateVO = mock(VMTemplateVO.class); + GenericSearchBuilder searchBuilder = mock(GenericSearchBuilder.class); + when(searchBuilder.entity()).thenReturn(templateVO); + SearchCriteriasearchCriteria = mock(SearchCriteria.class); + when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder); + when(searchBuilder.create()).thenReturn(searchCriteria); + mockTemplateZoneJoin(); + doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null); + List result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false); + assertNotNull(result); + verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId()); + verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, times(1)).and(eq("isIso"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, times(1)).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER)); + verify(templateDao, times(1)).customSearch(searchCriteria, null); + } + + @Test + public void testListTemplateIsoByArchAndZone_WithoutIsIso() { + Long dataCenterId = 1L; + CPU.CPUArch arch = CPU.CPUArch.getDefault(); + Boolean isIso = null; + VMTemplateVO templateVO = mock(VMTemplateVO.class); + GenericSearchBuilder searchBuilder = mock(GenericSearchBuilder.class); + when(searchBuilder.entity()).thenReturn(templateVO); + SearchCriteriasearchCriteria = mock(SearchCriteria.class); + when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder); + when(searchBuilder.create()).thenReturn(searchCriteria); + mockTemplateZoneJoin(); + doReturn(new ArrayList<>()).when(templateDao).customSearch(searchCriteria, null); + List result = templateDao.listTemplateIsoByArchVnfAndZone(dataCenterId, arch, isIso, false); + assertNotNull(result); + verify(searchBuilder, times(1)).select(null, SearchCriteria.Func.DISTINCT, templateVO.getGuestOSId()); + verify(searchBuilder, times(1)).and(eq("state"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("type"), any(), eq(SearchCriteria.Op.IN)); + verify(searchBuilder, times(1)).and(eq("arch"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, never()).and(eq("isIso"), any(), eq(SearchCriteria.Op.NEQ)); + verify(searchBuilder, never()).and(eq("isIso"), any(), eq(SearchCriteria.Op.EQ)); + verify(searchBuilder, times(1)).join(eq("templateZoneSearch"), any(), any(), any(), eq(JoinBuilder.JoinType.INNER)); + verify(templateDao, times(1)).customSearch(searchCriteria, null); + } + + @Test + public void testListIdsByExtensionId_ReturnsIds() { + long extensionId = 42L; + List expectedIds = Arrays.asList(1L, 2L, 3L); + GenericSearchBuilder searchBuilder = mock(GenericSearchBuilder.class); + SearchCriteria searchCriteria = mock(SearchCriteria.class); + when(templateDao.createSearchBuilder(Long.class)).thenReturn(searchBuilder); + when(searchBuilder.entity()).thenReturn(mock(VMTemplateVO.class)); + when(searchBuilder.create()).thenReturn(searchCriteria); + doReturn(expectedIds).when(templateDao).customSearchIncludingRemoved(eq(searchCriteria), isNull()); + List result = templateDao.listIdsByExtensionId(extensionId); + assertEquals(expectedIds, result); + verify(searchCriteria).setParameters("extensionId", extensionId); + verify(templateDao).customSearchIncludingRemoved(eq(searchCriteria), isNull()); + } + @Test public void testFindSystemVMReadyTemplate() { Long zoneId = 1L; @@ -207,4 +340,82 @@ public void testFindSystemVMReadyTemplate() { VMTemplateVO readyTemplate = templateDao.findSystemVMReadyTemplate(zoneId, Hypervisor.HypervisorType.KVM, CPU.CPUArch.arm64.getType()); Assert.assertEquals(CPU.CPUArch.arm64, readyTemplate.getArch()); } + + @Test + public void findActiveSystemTemplateByHypervisorArchAndUrlPath_ReturnsTemplate() { + VMTemplateVO expectedTemplate = mock(VMTemplateVO.class); + SearchBuilder sb = mock(SearchBuilder.class); + when(sb.entity()).thenReturn(expectedTemplate); + SearchCriteriasc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + when(templateDao.createSearchBuilder()).thenReturn(sb); + List templates = Collections.singletonList(expectedTemplate); + doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + + VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath( + Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "testPath"); + + assertNotNull(result); + assertEquals(expectedTemplate, result); + } + + @Test + public void findActiveSystemTemplateByHypervisorArchAndUrlPath_ReturnsNullWhenNoTemplatesFound() { + VMTemplateVO template = mock(VMTemplateVO.class); + SearchBuilder sb = mock(SearchBuilder.class); + when(sb.entity()).thenReturn(template); + SearchCriteriasc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + when(templateDao.createSearchBuilder()).thenReturn(sb); + doReturn(Collections.emptyList()).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + + VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath( + Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "testPath"); + + assertNull(result); + } + + @Test + public void findActiveSystemTemplateByHypervisorArchAndUrlPath_NullHypervisor() { + VMTemplateVO expectedTemplate = mock(VMTemplateVO.class); + SearchBuilder sb = mock(SearchBuilder.class); + when(sb.entity()).thenReturn(expectedTemplate); + SearchCriteriasc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + when(templateDao.createSearchBuilder()).thenReturn(sb); + List templates = Collections.singletonList(expectedTemplate); + doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + + VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath( + null, CPU.CPUArch.amd64, "testPath"); + + assertNotNull(result); + assertEquals(expectedTemplate, result); + } + + @Test + public void findActiveSystemTemplateByHypervisorArchAndUrlPath_NullArch() { + VMTemplateVO expectedTemplate = mock(VMTemplateVO.class); + SearchBuilder sb = mock(SearchBuilder.class); + when(sb.entity()).thenReturn(expectedTemplate); + SearchCriteriasc = mock(SearchCriteria.class); + when(sb.create()).thenReturn(sc); + when(templateDao.createSearchBuilder()).thenReturn(sb); + List templates = Collections.singletonList(expectedTemplate); + doReturn(templates).when(templateDao).listBy(any(SearchCriteria.class), any(Filter.class)); + + VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath( + Hypervisor.HypervisorType.KVM, null, "testPath"); + + assertNotNull(result); + assertEquals(expectedTemplate, result); + } + + @Test + public void findActiveSystemTemplateByHypervisorArchAndUrlPath_EmptyUrlPathSuffix() { + VMTemplateVO result = templateDao.findActiveSystemTemplateByHypervisorArchAndUrlPath( + Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, ""); + + assertNull(result); + } } diff --git a/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java b/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java index 9445efeb089c..6f153727ab76 100644 --- a/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java +++ b/engine/schema/src/test/java/com/cloud/storage/dao/VolumeDaoImplTest.java @@ -41,6 +41,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnitRunner; +import com.cloud.storage.Volume; import com.cloud.storage.VolumeVO; import com.cloud.utils.db.Filter; import com.cloud.utils.db.SearchBuilder; @@ -113,6 +114,69 @@ public void testListPoolIdsByVolumeCount_without_cluster_details() throws SQLExc verify(preparedStatementMock, times(1)).executeQuery(); } + @Test + public void findByInstanceAndNotState_queriesWithInstanceIdAndExcludedStates() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doReturn(new ArrayList<>()).when(volumeDao).listBy(sc); + Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb); + VolumeVO mockedVO = Mockito.mock(VolumeVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + + volumeDao.findByInstanceAndNotStates(42L, Volume.State.Ready); + + Mockito.verify(sc).setParameters("instanceId", 42L); + Mockito.verify(sc).setParameters("state", (Object[]) new Volume.State[]{Volume.State.Ready}); + } + + @Test + public void findByInstanceAndNotStates_withMultipleExcludedStates_passesAllStatesToCriteria() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doReturn(new ArrayList<>()).when(volumeDao).listBy(sc); + Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb); + VolumeVO mockedVO = Mockito.mock(VolumeVO.class); + Mockito.when(sb.entity()).thenReturn(mockedVO); + + volumeDao.findByInstanceAndNotStates(7L, Volume.State.Destroy, Volume.State.Expunged); + + Mockito.verify(sc).setParameters("instanceId", 7L); + Mockito.verify(sc).setParameters("state", + (Object[]) new Volume.State[]{Volume.State.Destroy, Volume.State.Expunged}); + } + + @Test + public void findByInstanceAndNotStates_returnsResultFromDao() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + VolumeVO vol = Mockito.mock(VolumeVO.class); + Mockito.doReturn(List.of(vol)).when(volumeDao).listBy(sc); + Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb); + Mockito.when(sb.entity()).thenReturn(Mockito.mock(VolumeVO.class)); + + List result = volumeDao.findByInstanceAndNotStates(1L, Volume.State.Ready); + + Assert.assertEquals(1, result.size()); + Assert.assertSame(vol, result.get(0)); + } + + @Test + public void findByInstanceAndNotStates_noMatchingVolumes_returnsEmptyList() { + SearchBuilder sb = Mockito.mock(SearchBuilder.class); + SearchCriteria sc = Mockito.mock(SearchCriteria.class); + Mockito.when(sb.create()).thenReturn(sc); + Mockito.doReturn(new ArrayList<>()).when(volumeDao).listBy(sc); + Mockito.when(volumeDao.createSearchBuilder()).thenReturn(sb); + Mockito.when(sb.entity()).thenReturn(Mockito.mock(VolumeVO.class)); + + List result = volumeDao.findByInstanceAndNotStates(99L, Volume.State.Ready); + + Assert.assertTrue(result.isEmpty()); + } + @Test public void testSearchRemovedByVmsNoVms() { Assert.assertTrue(CollectionUtils.isEmpty(volumeDao.searchRemovedByVms( @@ -141,5 +205,4 @@ public void testSearchRemovedByVms() { Mockito.any(SearchCriteria.class), Mockito.any(Filter.class), Mockito.eq(null), Mockito.eq(false)); } - } diff --git a/engine/schema/src/test/java/com/cloud/upgrade/ConfigurationGroupsAggregatorTest.java b/engine/schema/src/test/java/com/cloud/upgrade/ConfigurationGroupsAggregatorTest.java new file mode 100644 index 000000000000..bab36ef00cf8 --- /dev/null +++ b/engine/schema/src/test/java/com/cloud/upgrade/ConfigurationGroupsAggregatorTest.java @@ -0,0 +1,76 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package com.cloud.upgrade; + +import static org.mockito.Mockito.when; + +import java.util.Collections; + +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationGroupDao; +import org.apache.cloudstack.framework.config.dao.ConfigurationSubGroupDao; +import org.apache.cloudstack.framework.config.impl.ConfigurationSubGroupVO; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.logging.log4j.Logger; +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; + +@RunWith(MockitoJUnitRunner.class) +public class ConfigurationGroupsAggregatorTest { + @InjectMocks + private ConfigurationGroupsAggregator configurationGroupsAggregator = new ConfigurationGroupsAggregator(); + + @Mock + private ConfigurationDao configDao; + + @Mock + private ConfigurationGroupDao configGroupDao; + + @Mock + private ConfigurationSubGroupDao configSubGroupDao; + + @Mock + private Logger logger; + + @Test + public void testUpdateConfigurationGroups() { + ConfigurationVO config = new ConfigurationVO("Advanced", "DEFAULT", "management-server", + "test.config.name", null, "description"); + config.setGroupId(1L); + config.setSubGroupId(1L); + + when(configDao.searchPartialConfigurations()).thenReturn(Collections.singletonList(config)); + + ConfigurationSubGroupVO configSubGroup = Mockito.mock(ConfigurationSubGroupVO.class); + when(configSubGroupDao.findByName("name")).thenReturn(configSubGroup); + Mockito.when(configSubGroup.getId()).thenReturn(10L); + Mockito.when(configSubGroup.getGroupId()).thenReturn(5L); + + configurationGroupsAggregator.updateConfigurationGroups(); + + Assert.assertEquals(Long.valueOf(5), config.getGroupId()); + Assert.assertEquals(Long.valueOf(10), config.getSubGroupId()); + Mockito.verify(configDao, Mockito.times(1)).persist(config); + Mockito.verify(logger, Mockito.times(1)).debug("Updating configuration groups"); + Mockito.verify(logger, Mockito.times(1)).debug("Successfully updated configuration groups."); + } +} diff --git a/engine/schema/src/test/java/com/cloud/upgrade/DatabaseUpgradeCheckerTest.java b/engine/schema/src/test/java/com/cloud/upgrade/DatabaseUpgradeCheckerTest.java index 1b84fc3a9e33..884398cf410d 100644 --- a/engine/schema/src/test/java/com/cloud/upgrade/DatabaseUpgradeCheckerTest.java +++ b/engine/schema/src/test/java/com/cloud/upgrade/DatabaseUpgradeCheckerTest.java @@ -45,6 +45,8 @@ import com.cloud.upgrade.dao.Upgrade41510to41520; import com.cloud.upgrade.dao.Upgrade41610to41700; import com.cloud.upgrade.dao.Upgrade42020to42030; +import com.cloud.upgrade.dao.Upgrade42030to42040; +import com.cloud.upgrade.dao.Upgrade42040to42100; import com.cloud.upgrade.dao.Upgrade452to453; import com.cloud.upgrade.dao.Upgrade453to460; import com.cloud.upgrade.dao.Upgrade460to461; @@ -399,6 +401,25 @@ public void testCalculateUpgradePath42010to42030() { assertArrayEquals(new String[]{"4.20.2.0", "4.20.3.0"}, upgrades[0].getUpgradableVersionRange()); assertEquals(currentVersion.toString(), upgrades[0].getUpgradedVersion()); + } + + @Test + public void testCalculateUpgradePath42010to42100() { + final CloudStackVersion dbVersion = CloudStackVersion.parse("4.20.1.0"); + assertNotNull(dbVersion); + + final CloudStackVersion currentVersion = CloudStackVersion.parse("4.21.0.0"); + assertNotNull(currentVersion); + + final DatabaseUpgradeChecker checker = new DatabaseUpgradeChecker(); + final DbUpgrade[] upgrades = checker.calculateUpgradePath(dbVersion, currentVersion); + + assertNotNull(upgrades); + assertEquals(3, upgrades.length); + assertTrue(upgrades[0] instanceof Upgrade42020to42030); + assertTrue(upgrades[1] instanceof Upgrade42030to42040); + assertTrue(upgrades[2] instanceof Upgrade42040to42100); + assertEquals(currentVersion.toString(), upgrades[2].getUpgradedVersion()); } } 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 aad8760ed77a..51db952eb613 100644 --- a/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java +++ b/engine/schema/src/test/java/com/cloud/upgrade/SystemVmTemplateRegistrationTest.java @@ -17,6 +17,7 @@ package com.cloud.upgrade; +import static com.cloud.upgrade.SystemVmTemplateRegistration.DEFAULT_SYSTEM_VM_GUEST_OS_NAME; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -25,24 +26,41 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Date; import java.util.List; +import java.util.Map; +import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine; +import org.apache.cloudstack.framework.config.dao.ConfigurationDao; +import org.apache.cloudstack.framework.config.impl.ConfigurationVO; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreDetailsDao; +import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.utils.security.DigestHelper; import org.apache.commons.lang3.StringUtils; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; @@ -53,15 +71,27 @@ import org.mockito.junit.MockitoJUnitRunner; import com.cloud.cpu.CPU; +import com.cloud.dc.DataCenterVO; import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +import com.cloud.dc.dao.DataCenterDetailsDao; import com.cloud.hypervisor.Hypervisor; +import com.cloud.storage.DataStoreRole; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.Storage; +import com.cloud.storage.VMTemplateStorageResourceAssoc; import com.cloud.storage.VMTemplateVO; +import com.cloud.storage.VMTemplateZoneVO; +import com.cloud.storage.dao.GuestOSDao; import com.cloud.storage.dao.VMTemplateDao; +import com.cloud.storage.dao.VMTemplateZoneDao; +import com.cloud.template.VirtualMachineTemplate; import com.cloud.utils.HttpUtils; import com.cloud.utils.Pair; import com.cloud.utils.UriUtils; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; +import com.cloud.vm.dao.VMInstanceDao; @RunWith(MockitoJUnitRunner.class) public class SystemVmTemplateRegistrationTest { @@ -72,10 +102,42 @@ public class SystemVmTemplateRegistrationTest { @Mock VMTemplateDao vmTemplateDao; + @Mock + GuestOSDao guestOSDao; + + @Mock + TemplateDataStoreDao templateDataStoreDao; + + @Mock + ConfigurationDao configurationDao; + + @Mock + DataCenterDao dataCenterDao; + + @Mock + DataCenterDetailsDao dataCenterDetailsDao; + + @Mock + VMTemplateZoneDao vmTemplateZoneDao; + + @Mock + ImageStoreDao imageStoreDao; + + @Mock + ImageStoreDetailsDao imageStoreDetailsDao; + + @Mock + VMInstanceDao vmInstanceDao; + @Spy @InjectMocks SystemVmTemplateRegistration systemVmTemplateRegistration = new SystemVmTemplateRegistration(); + @Before + public void setup() { + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.clear(); + } + private void setupMetadataFile(MockedStatic mockedStatic, String content) { try { String location = "metadata.ini"; @@ -98,7 +160,7 @@ public void test_parseMetadataFile_noFile() { setupMetadataFile(mockedStatic, null); CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, SystemVmTemplateRegistration::parseMetadataFile); - assertTrue(exception.getMessage().contains("Failed to parse systemVM Template metadata file")); + assertTrue(exception.getMessage().contains("Failed to parse system VM Template metadata file")); } } @@ -109,7 +171,7 @@ public void test_parseMetadataFile_invalidContent() { setupMetadataFile(mockedStatic, "abc"); CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, SystemVmTemplateRegistration::parseMetadataFile); - assertTrue(exception.getMessage().contains("Failed to parse systemVM Template metadata file")); + assertTrue(exception.getMessage().contains("Failed to parse system VM Template metadata file")); } } @@ -141,21 +203,25 @@ public void test_parseMetadataFile_success() { String version = SystemVmTemplateRegistration.parseMetadataFile(); assertEquals("x.y.z.0", version); } - assertNull(SystemVmTemplateRegistration.NewTemplateMap.get("xenserver")); + assertNull(SystemVmTemplateRegistration.getMetadataTemplateDetails(Hypervisor.HypervisorType.XenServer, + CPU.CPUArch.getDefault())); SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = - SystemVmTemplateRegistration.NewTemplateMap.get("kvm-x86_64"); + SystemVmTemplateRegistration.getMetadataTemplateDetails(Hypervisor.HypervisorType.KVM, + CPU.CPUArch.amd64); assertNotNull(templateDetails); assertEquals(CPU.CPUArch.amd64, templateDetails.getArch()); assertEquals(Hypervisor.HypervisorType.KVM, templateDetails.getHypervisorType()); templateDetails = - SystemVmTemplateRegistration.NewTemplateMap.get("kvm-aarch64"); + SystemVmTemplateRegistration.getMetadataTemplateDetails(Hypervisor.HypervisorType.KVM, + CPU.CPUArch.arm64); assertNotNull(templateDetails); assertEquals(CPU.CPUArch.arm64, templateDetails.getArch()); assertEquals(Hypervisor.HypervisorType.KVM, templateDetails.getHypervisorType()); templateDetails = - SystemVmTemplateRegistration.NewTemplateMap.get("vmware"); + SystemVmTemplateRegistration.getMetadataTemplateDetails(Hypervisor.HypervisorType.VMware, + CPU.CPUArch.getDefault()); assertNotNull(templateDetails); - assertEquals(CPU.CPUArch.amd64, templateDetails.getArch()); + assertEquals(CPU.CPUArch.getDefault(), templateDetails.getArch()); assertEquals(Hypervisor.HypervisorType.VMware, templateDetails.getHypervisorType()); } @@ -193,11 +259,10 @@ public void testValidateTemplateFile_fileNotFound() { SystemVmTemplateRegistration.MetadataTemplateDetails details = new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM, "name", "file", "url", "checksum", CPU.CPUArch.amd64, "guestos"); - SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey( - details.getHypervisorType(), details.getArch()), details); + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.add(details); doReturn(null).when(systemVmTemplateRegistration).getTemplateFile(details); try { - systemVmTemplateRegistration.validateTemplateFileForHypervisorAndArch(details.getHypervisorType(), + systemVmTemplateRegistration.getValidatedTemplateDetailsForHypervisorAndArch(details.getHypervisorType(), details.getArch()); fail("Expected CloudRuntimeException due to missing template file"); } catch (CloudRuntimeException e) { @@ -211,12 +276,11 @@ public void testValidateTemplateFile_checksumMismatch() { new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM, "name", "file", "url", "checksum", CPU.CPUArch.amd64, "guestos"); File dummyFile = new File("dummy.txt"); - SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey( - details.getHypervisorType(), details.getArch()), details); + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.add(details); doReturn(dummyFile).when(systemVmTemplateRegistration).getTemplateFile(details); - doReturn(true).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, dummyFile); - try { - systemVmTemplateRegistration.validateTemplateFileForHypervisorAndArch(details.getHypervisorType(), + try (MockedStatic digestMock = Mockito.mockStatic(DigestHelper.class)) { + digestMock.when(() -> DigestHelper.calculateChecksum(dummyFile)).thenReturn("differentChecksum"); + systemVmTemplateRegistration.getValidatedTemplateDetailsForHypervisorAndArch(details.getHypervisorType(), details.getArch()); fail("Expected CloudRuntimeException due to checksum failure"); } catch (CloudRuntimeException e) { @@ -230,42 +294,55 @@ public void testValidateTemplateFile_success() { new SystemVmTemplateRegistration.MetadataTemplateDetails(Hypervisor.HypervisorType.KVM, "name", "file", "url", "checksum", CPU.CPUArch.amd64, "guestos"); File dummyFile = new File("dummy.txt"); - SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey( - details.getHypervisorType(), details.getArch()), details); + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.add(details); doReturn(dummyFile).when(systemVmTemplateRegistration).getTemplateFile(details); - doReturn(false).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, dummyFile); - systemVmTemplateRegistration.validateTemplateFileForHypervisorAndArch(details.getHypervisorType(), - details.getArch()); + try (MockedStatic digestMock = Mockito.mockStatic(DigestHelper.class)) { + digestMock.when(() -> DigestHelper.calculateChecksum(dummyFile)).thenReturn("checksum"); + systemVmTemplateRegistration.getValidatedTemplateDetailsForHypervisorAndArch(details.getHypervisorType(), + details.getArch()); + } } @Test - public void testValidateAndRegisterTemplate() { + public void testValidateAndAddExistingTemplateToStore() { + long zoneId = 1L; Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.KVM; - String name = "TestTemplate"; - Long storeId = 123L; VMTemplateVO templateVO = new VMTemplateVO(); - templateVO.setArch(CPU.CPUArch.x86); + templateVO.setHypervisorType(hypervisor); + templateVO.setArch(CPU.CPUArch.getDefault()); TemplateDataStoreVO templateDataStoreVO = new TemplateDataStoreVO(); + Long storeId = 123L; String filePath = "/dummy/path"; - doNothing().when(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(hypervisor, templateVO.getArch()); - doNothing().when(systemVmTemplateRegistration).registerTemplate(hypervisor, name, storeId, templateVO, templateDataStoreVO, filePath); - systemVmTemplateRegistration.validateAndRegisterTemplate(hypervisor, name, storeId, templateVO, templateDataStoreVO, filePath); - verify(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(eq(hypervisor), eq(templateVO.getArch())); - verify(systemVmTemplateRegistration).registerTemplate(eq(hypervisor), eq(name), eq(storeId), eq(templateVO), eq(templateDataStoreVO), eq(filePath)); + SystemVmTemplateRegistration.MetadataTemplateDetails details = + mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + doReturn(details).when(systemVmTemplateRegistration) + .getValidatedTemplateDetailsForHypervisorAndArch(hypervisor, templateVO.getArch()); + doNothing().when(systemVmTemplateRegistration).addExistingTemplateToStore(templateVO, details, + templateDataStoreVO, zoneId, storeId, filePath); + systemVmTemplateRegistration.validateAndAddTemplateToStore(templateVO, templateDataStoreVO, zoneId, storeId, + filePath); + verify(systemVmTemplateRegistration) + .getValidatedTemplateDetailsForHypervisorAndArch(hypervisor, templateVO.getArch()); + verify(systemVmTemplateRegistration).addExistingTemplateToStore(templateVO, details, templateDataStoreVO, + zoneId, storeId, filePath); } @Test - public void testValidateAndRegisterTemplateForNonExistingEntries() { + public void testValidateAndAddExistingTemplateToStoreForNonExistingEntries() { + long zoneId = 1L; Hypervisor.HypervisorType hypervisor = Hypervisor.HypervisorType.KVM; CPU.CPUArch arch = CPU.CPUArch.amd64; String name = "TestTemplateNonExisting"; - Pair storeUrlAndId = new Pair<>("nfs://dummy", 456L); + long storeId = 123L; String filePath = "/dummy/path/nonexisting"; - doNothing().when(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(hypervisor, arch); - doNothing().when(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(hypervisor, arch, name, storeUrlAndId, filePath); - systemVmTemplateRegistration.validateAndRegisterTemplateForNonExistingEntries(hypervisor, arch, name, storeUrlAndId, filePath); - verify(systemVmTemplateRegistration).validateTemplateFileForHypervisorAndArch(eq(hypervisor), eq(arch)); - verify(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(eq(hypervisor), eq(arch), eq(name), eq(storeUrlAndId), eq(filePath)); + SystemVmTemplateRegistration.MetadataTemplateDetails details = + mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + doReturn(details).when(systemVmTemplateRegistration) + .getValidatedTemplateDetailsForHypervisorAndArch(hypervisor, arch); + doNothing().when(systemVmTemplateRegistration).registerNewTemplate(name, details, zoneId, storeId, filePath); + systemVmTemplateRegistration.validateAndRegisterNewTemplate(hypervisor, arch, name, zoneId, storeId, filePath); + verify(systemVmTemplateRegistration).getValidatedTemplateDetailsForHypervisorAndArch(hypervisor, arch); + verify(systemVmTemplateRegistration).registerNewTemplate(name, details, zoneId, storeId, filePath); } @Test @@ -316,86 +393,73 @@ public void testGetTemplateFile_fileDoesNotExist_downloadSucceeds() { } @Test - public void testIsTemplateFileChecksumDifferent_noMismatch() { - SystemVmTemplateRegistration.MetadataTemplateDetails details = - Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); - when(details.getChecksum()).thenReturn("dummyChecksum"); - File file = new File("dummy.txt"); - try (MockedStatic digestMock = Mockito.mockStatic(DigestHelper.class)) { - digestMock.when(() -> DigestHelper.calculateChecksum(file)).thenReturn("dummyChecksum"); - boolean result = systemVmTemplateRegistration.isTemplateFileChecksumDifferent(details, file); - assertFalse(result); - } - } - - @Test - public void testIsTemplateFileChecksumDifferent_mismatch() { - SystemVmTemplateRegistration.MetadataTemplateDetails details = - Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); - when(details.getChecksum()).thenReturn("expectedChecksum"); - File file = new File("dummy.txt"); - try (MockedStatic digestMock = Mockito.mockStatic(DigestHelper.class)) { - digestMock.when(() -> DigestHelper.calculateChecksum(file)).thenReturn("actualChecksum"); - boolean result = systemVmTemplateRegistration.isTemplateFileChecksumDifferent(details, file); - assertTrue(result); - } - } - - @Test(expected = CloudRuntimeException.class) - public void testValidateTemplates_metadataTemplateFailure() { + public void testValidateTemplates_metadataTemplateSkip() { + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.VMware; + CPU.CPUArch arch = CPU.CPUArch.arm64; List> list = new ArrayList<>(); - list.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64)); + list.add(new Pair<>(hypervisorType, arch)); systemVmTemplateRegistration.validateTemplates(list); + verify(systemVmTemplateRegistration, never()).getValidatedTemplateDetailsForHypervisorAndArch(hypervisorType, + arch); } @Test(expected = CloudRuntimeException.class) public void testValidateTemplates_fileFailure() { + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.amd64; List> list = new ArrayList<>(); - list.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64)); - + list.add(new Pair<>(hypervisorType, arch)); SystemVmTemplateRegistration.MetadataTemplateDetails details = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); - SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey( - Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), details); + when(details.getHypervisorType()).thenReturn(hypervisorType); + when(details.getArch()).thenReturn(arch); File mockFile = Mockito.mock(File.class); + when(details.isFileChecksumDifferent(mockFile)).thenReturn(true); + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.add(details); doReturn(mockFile).when(systemVmTemplateRegistration).getTemplateFile(details); - doReturn(true).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, mockFile); systemVmTemplateRegistration.validateTemplates(list); } + @Test(expected = CloudRuntimeException.class) public void testValidateTemplates_downloadableFileNotFound() { CPU.CPUArch arch = SystemVmTemplateRegistration.DOWNLOADABLE_TEMPLATE_ARCH_TYPES.get(0); List> list = new ArrayList<>(); - list.add(new Pair<>(Hypervisor.HypervisorType.KVM, arch)); + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + list.add(new Pair<>(hypervisorType, arch)); SystemVmTemplateRegistration.MetadataTemplateDetails details = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); - SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey( - Hypervisor.HypervisorType.KVM, arch), details); + when(details.getHypervisorType()).thenReturn(hypervisorType); + when(details.getArch()).thenReturn(arch); + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.add(details); doReturn(null).when(systemVmTemplateRegistration).getTemplateFile(details); systemVmTemplateRegistration.validateTemplates(list); } @Test public void testValidateTemplates_success() { + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.amd64; List> list = new ArrayList<>(); - list.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64)); - + list.add(new Pair<>(hypervisorType, arch)); SystemVmTemplateRegistration.MetadataTemplateDetails details = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); - SystemVmTemplateRegistration.NewTemplateMap.put(SystemVmTemplateRegistration.getHypervisorArchKey( - Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64), details); + when(details.getHypervisorType()).thenReturn(hypervisorType); + when(details.getArch()).thenReturn(arch); File mockFile = Mockito.mock(File.class); + when(details.isFileChecksumDifferent(mockFile)).thenReturn(false); + SystemVmTemplateRegistration.METADATA_TEMPLATE_LIST.add(details); doReturn(mockFile).when(systemVmTemplateRegistration).getTemplateFile(details); - doReturn(false).when(systemVmTemplateRegistration).isTemplateFileChecksumDifferent(details, mockFile); systemVmTemplateRegistration.validateTemplates(list); } @Test - public void testRegisterTemplatesForZone() { + public void testAddExistingTemplatesForZoneToStore() { long zoneId = 1L; String filePath = "dummyFilePath"; String nfsVersion = "nfs3"; Pair storeUrlAndId = new Pair<>("nfs://dummy", 100L); + String name = "existing"; + String url = "url"; doReturn(storeUrlAndId).when(systemVmTemplateRegistration).getNfsStoreInZone(zoneId); doReturn(nfsVersion).when(systemVmTemplateRegistration).getNfsVersion(storeUrlAndId.second()); try (MockedStatic mockedStatic = Mockito.mockStatic( @@ -404,24 +468,1174 @@ public void testRegisterTemplatesForZone() { Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; CPU.CPUArch arch = CPU.CPUArch.getDefault(); hypervisorArchList.add(new Pair<>(hypervisorType, arch)); - doReturn(hypervisorArchList).when(clusterDao).listDistinctHypervisorsArchAcrossClusters(zoneId); + doReturn(hypervisorArchList).when(clusterDao).listDistinctHypervisorsAndArchExcludingExternalType(zoneId); SystemVmTemplateRegistration.MetadataTemplateDetails details = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); - String name = "existing"; - Mockito.when(details.getArch()).thenReturn(CPU.CPUArch.getDefault()); - Mockito.when(details.getName()).thenReturn(name); + when(details.getArch()).thenReturn(CPU.CPUArch.getDefault()); + when(details.getName()).thenReturn(name); + when(details.getUrl()).thenReturn(url); mockedStatic.when(() -> SystemVmTemplateRegistration.getMetadataTemplateDetails(Mockito.any(), Mockito.any())).thenReturn(details); - when(systemVmTemplateRegistration.getRegisteredTemplate(name, arch)) - .thenReturn(null); - doNothing().when(systemVmTemplateRegistration).registerTemplateForNonExistingEntries( - hypervisorType, arch, - name, storeUrlAndId, filePath); + doNothing().when(systemVmTemplateRegistration).registerNewTemplate(name, details, zoneId, + storeUrlAndId.second(), filePath); systemVmTemplateRegistration.registerTemplatesForZone(zoneId, filePath); mockedStatic.verify(() -> SystemVmTemplateRegistration.mountStore(storeUrlAndId.first(), filePath, nfsVersion)); - verify(systemVmTemplateRegistration).registerTemplateForNonExistingEntries(hypervisorType, - arch, name, storeUrlAndId, filePath); + verify(systemVmTemplateRegistration).registerNewTemplate(name, details, zoneId, + storeUrlAndId.second(), filePath); + } + } + + @Test + public void updateOrRegisterSystemVmTemplate_UpdatesRegisteredTemplate() { + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + when(templateDetails.getName()).thenReturn("templateName"); + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + when(templateDetails.getUrl()).thenReturn("http://example.com/template"); + VMTemplateVO registeredTemplate = Mockito.mock(VMTemplateVO.class); + when(registeredTemplate.getId()).thenReturn(1L); + doReturn(registeredTemplate).when(systemVmTemplateRegistration).getRegisteredTemplate( + "templateName", Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "http://example.com/template"); + doNothing().when(systemVmTemplateRegistration).updateRegisteredTemplateDetails(1L, templateDetails, + null); + + boolean result = systemVmTemplateRegistration.updateOrRegisterSystemVmTemplate(templateDetails, + new ArrayList<>()); + + assertFalse(result); + verify(systemVmTemplateRegistration).updateRegisteredTemplateDetails(1L, templateDetails, null); + } + + @Test + public void updateOrRegisterSystemVmTemplate_SkipsUnusedHypervisorArch() { + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + when(templateDetails.getName()).thenReturn("templateName"); + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + when(templateDetails.getUrl()).thenReturn("http://example.com/template"); + doReturn(null).when(systemVmTemplateRegistration).getRegisteredTemplate( + "templateName", Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "http://example.com/template"); + doReturn(null).when(vmTemplateDao).findLatestTemplateByTypeAndHypervisorAndArch( + Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, Storage.TemplateType.SYSTEM); + + boolean result = systemVmTemplateRegistration.updateOrRegisterSystemVmTemplate(templateDetails, new ArrayList<>()); + + assertFalse(result); + verify(systemVmTemplateRegistration, never()).registerTemplates(anyList()); + } + + @Test + public void updateOrRegisterSystemVmTemplate_RegistersNewTemplate() { + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + when(templateDetails.getName()).thenReturn("templateName"); + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + when(templateDetails.getUrl()).thenReturn("http://example.com/template"); + doReturn(null).when(systemVmTemplateRegistration).getRegisteredTemplate( + "templateName", Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "http://example.com/template"); + List> hypervisorsInUse = new ArrayList<>(); + hypervisorsInUse.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64)); + doNothing().when(systemVmTemplateRegistration).registerTemplates(hypervisorsInUse); + + boolean result = systemVmTemplateRegistration.updateOrRegisterSystemVmTemplate(templateDetails, hypervisorsInUse); + + assertTrue(result); + verify(systemVmTemplateRegistration).registerTemplates(eq(hypervisorsInUse)); + } + + @Test + public void updateOrRegisterSystemVmTemplate_ThrowsExceptionOnRegistrationFailure() { + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + when(templateDetails.getName()).thenReturn("templateName"); + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + when(templateDetails.getUrl()).thenReturn("http://example.com/template"); + doReturn(null).when(systemVmTemplateRegistration).getRegisteredTemplate( + "templateName", Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64, "http://example.com/template"); + List> hypervisorsInUse = new ArrayList<>(); + hypervisorsInUse.add(new Pair<>(Hypervisor.HypervisorType.KVM, CPU.CPUArch.amd64)); + doThrow(new CloudRuntimeException("Registration failed")).when(systemVmTemplateRegistration).registerTemplates(hypervisorsInUse); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + () -> systemVmTemplateRegistration.updateOrRegisterSystemVmTemplate(templateDetails, hypervisorsInUse)); + + assertTrue(exception.getMessage().contains("Failed to register")); + } + + @Test + public void updateRegisteredTemplateDetails_UpdatesTemplateSuccessfully() { + Long templateId = 1L; + Long zoneId = 2L; + 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(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); + + systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId); + + verify(templateVO).setTemplateType(Storage.TemplateType.SYSTEM); + verify(templateVO).setGuestOSId(10); + verify(vmTemplateDao).update(templateVO.getId(), templateVO); + verify(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM); + verify(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM, + templateName, zoneId); + } + + @Test + public void updateRegisteredTemplateDetails_ThrowsExceptionWhenUpdateFails() { + Long templateId = 1L; + Long zoneId = 2L; + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = + Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class); + + when(templateDetails.getGuestOs()).thenReturn("Debian"); + when(vmTemplateDao.findById(templateId)).thenReturn(templateVO); + when(vmTemplateDao.update(templateVO.getId(), templateVO)).thenReturn(false); + + CloudRuntimeException exception = assertThrows(CloudRuntimeException.class, + () -> systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId)); + + assertTrue(exception.getMessage().contains("Exception while updating template with id")); + verify(systemVmTemplateRegistration, never()).updateSystemVMEntries(anyLong(), any()); + verify(systemVmTemplateRegistration, never()).updateConfigurationParams(any(), any(), any()); + } + + @Test + public void updateRegisteredTemplateDetails_SkipsGuestOSUpdateWhenNotFound() { + Long templateId = 1L; + Long zoneId = 2L; + 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(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); + + systemVmTemplateRegistration.updateRegisteredTemplateDetails(templateId, templateDetails, zoneId); + + verify(templateVO, never()).setGuestOSId(anyInt()); + verify(vmTemplateDao).update(templateVO.getId(), templateVO); + verify(systemVmTemplateRegistration).updateSystemVMEntries(templateId, Hypervisor.HypervisorType.KVM); + verify(systemVmTemplateRegistration).updateConfigurationParams(Hypervisor.HypervisorType.KVM, + templateName, zoneId); + } + + @Test + public void registerTemplatesForZone_SuccessfullyRegistersNewTemplate() { + long zoneId = 1L; + String storeMountPath = "/mnt/nfs"; + Pair storeUrlAndId = new Pair<>("nfs://dummy", 100L); + String nfsVersion = "nfs3"; + List> hypervisorArchList = new ArrayList<>(); + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.amd64; + hypervisorArchList.add(new Pair<>(hypervisorType, arch)); + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = + Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + when(templateDetails.getHypervisorType()).thenReturn(hypervisorType); + when(templateDetails.getArch()).thenReturn(arch); + String name = "TestTemplate"; + String url = "http://example.com/template"; + when(templateDetails.getName()).thenReturn(name); + when(templateDetails.getUrl()).thenReturn(url); + doReturn(storeUrlAndId).when(systemVmTemplateRegistration).getNfsStoreInZone(zoneId); + doReturn(nfsVersion).when(systemVmTemplateRegistration).getNfsVersion(storeUrlAndId.second()); + doReturn(null).when(systemVmTemplateRegistration).getRegisteredTemplate( + name, hypervisorType, arch, url); + doNothing().when(systemVmTemplateRegistration).registerNewTemplate( + name, templateDetails, zoneId, storeUrlAndId.second(), storeMountPath); + doReturn(hypervisorArchList).when(clusterDao).listDistinctHypervisorsAndArchExcludingExternalType(zoneId); + try (MockedStatic mockedStatic = + Mockito.mockStatic(SystemVmTemplateRegistration.class)) { + mockedStatic.when(() -> SystemVmTemplateRegistration.getMetadataTemplateDetails( + hypervisorType, arch)).thenReturn(templateDetails); + + systemVmTemplateRegistration.registerTemplatesForZone(zoneId, storeMountPath); + + mockedStatic.verify(() -> SystemVmTemplateRegistration.mountStore( + eq(storeUrlAndId.first()), eq(storeMountPath), eq(nfsVersion)), times(1)); + verify(systemVmTemplateRegistration).registerNewTemplate( + templateDetails.getName(), templateDetails, zoneId, storeUrlAndId.second(), storeMountPath); } } + + @Test + public void registerTemplatesForZone_SkipsWhenTemplateDetailsNotFound() { + long zoneId = 1L; + String storeMountPath = "/mnt/nfs"; + Pair storeUrlAndId = new Pair<>("nfs://dummy", 100L); + String nfsVersion = "nfs3"; + List> hypervisorArchList = new ArrayList<>(); + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.amd64; + hypervisorArchList.add(new Pair<>(hypervisorType, arch)); + doReturn(storeUrlAndId).when(systemVmTemplateRegistration).getNfsStoreInZone(zoneId); + doReturn(nfsVersion).when(systemVmTemplateRegistration).getNfsVersion(storeUrlAndId.second()); + doReturn(hypervisorArchList).when(clusterDao).listDistinctHypervisorsAndArchExcludingExternalType(zoneId); + + try (MockedStatic mockedStatic = + Mockito.mockStatic(SystemVmTemplateRegistration.class)) { + mockedStatic.when(() -> SystemVmTemplateRegistration.getMetadataTemplateDetails( + hypervisorType, arch)).thenReturn(null); + + systemVmTemplateRegistration.registerTemplatesForZone(zoneId, storeMountPath); + + mockedStatic.verify(() -> SystemVmTemplateRegistration.mountStore( + eq(storeUrlAndId.first()), eq(storeMountPath), eq(nfsVersion)), times(1)); + verify(systemVmTemplateRegistration, never()).registerNewTemplate(any(), any(), anyLong(), anyLong(), any()); + } + } + + @Test + public void registerTemplatesForZone_AddsExistingTemplateToStore() { + long zoneId = 1L; + String storeMountPath = "/mnt/nfs"; + Pair storeUrlAndId = new Pair<>("nfs://dummy", 100L); + String nfsVersion = "nfs3"; + List> hypervisorArchList = new ArrayList<>(); + Hypervisor.HypervisorType hypervisorType = Hypervisor.HypervisorType.KVM; + CPU.CPUArch arch = CPU.CPUArch.amd64; + hypervisorArchList.add(new Pair<>(hypervisorType, arch)); + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = + Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + when(templateDetails.getHypervisorType()).thenReturn(hypervisorType); + when(templateDetails.getArch()).thenReturn(arch); + String name = "TestTemplate"; + String url = "http://example.com/template"; + when(templateDetails.getName()).thenReturn(name); + when(templateDetails.getUrl()).thenReturn(url); + VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class); + long templateId = 100L; + when(templateVO.getId()).thenReturn(templateId); + TemplateDataStoreVO templateDataStoreVO = Mockito.mock(TemplateDataStoreVO.class); + String installPath = "/template/install/path"; + when(templateDataStoreVO.getInstallPath()).thenReturn(installPath); + + doReturn(storeUrlAndId).when(systemVmTemplateRegistration).getNfsStoreInZone(zoneId); + doReturn(nfsVersion).when(systemVmTemplateRegistration).getNfsVersion(storeUrlAndId.second()); + doReturn(hypervisorArchList).when(clusterDao).listDistinctHypervisorsAndArchExcludingExternalType(zoneId); + doReturn(templateVO).when(systemVmTemplateRegistration).getRegisteredTemplate(name, hypervisorType, arch, url); + doReturn(templateDataStoreVO).when(templateDataStoreDao) + .findByStoreTemplate(storeUrlAndId.second(), templateId); + doReturn(false).when(systemVmTemplateRegistration).validateIfSeeded( + templateDataStoreVO, storeUrlAndId.first(), installPath, nfsVersion); + doNothing().when(systemVmTemplateRegistration).addExistingTemplateToStore( + templateVO, templateDetails, templateDataStoreVO, zoneId, storeUrlAndId.second(), storeMountPath); + doNothing().when(systemVmTemplateRegistration).updateRegisteredTemplateDetails( + templateId, templateDetails, zoneId); + + try (MockedStatic mockedStatic = + Mockito.mockStatic(SystemVmTemplateRegistration.class)) { + mockedStatic.when(() -> SystemVmTemplateRegistration.getMetadataTemplateDetails( + hypervisorType, arch)).thenReturn(templateDetails); + + systemVmTemplateRegistration.registerTemplatesForZone(zoneId, storeMountPath); + + verify(systemVmTemplateRegistration).addExistingTemplateToStore( + templateVO, templateDetails, templateDataStoreVO, zoneId, storeUrlAndId.second(), storeMountPath); + verify(systemVmTemplateRegistration).updateRegisteredTemplateDetails(templateId, templateDetails, zoneId); + } + } + + @Test + public void performTemplateRegistrationOperations_CreatesNewTemplateWhenNotExists() { + String name = "TestTemplate"; + String url = "http://example.com/template"; + String checksum = "abc123"; + Storage.ImageFormat format = Storage.ImageFormat.QCOW2; + long guestOsId = 1L; + Long storeId = 100L; + Long templateId = null; + String filePath = "/mnt/nfs"; + TemplateDataStoreVO templateDataStoreVO = null; + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + doReturn(new VMTemplateVO()).when(vmTemplateDao).persist(any()); + doNothing().when(systemVmTemplateRegistration).createCrossZonesTemplateZoneRefEntries(anyLong()); + doNothing().when(systemVmTemplateRegistration).createTemplateStoreRefEntry(any()); + doNothing().when(systemVmTemplateRegistration).setupTemplateOnStore(anyString(), any(), anyString()); + doNothing().when(systemVmTemplateRegistration).readTemplateProperties(anyString(), any()); + doNothing().when(systemVmTemplateRegistration).updateTemplateDetails(any()); + + Long result = systemVmTemplateRegistration.performTemplateRegistrationOperations(name, templateDetails, url, checksum, format, guestOsId, storeId, templateId, filePath, templateDataStoreVO); + + assertNotNull(result); + verify(vmTemplateDao).persist(any()); + verify(systemVmTemplateRegistration).createCrossZonesTemplateZoneRefEntries(anyLong()); + verify(systemVmTemplateRegistration).createTemplateStoreRefEntry(any()); + verify(systemVmTemplateRegistration).setupTemplateOnStore(anyString(), any(), anyString()); + verify(systemVmTemplateRegistration).updateTemplateDetails(any()); + } + + @Test + public void performTemplateRegistrationOperations_UpdatesExistingTemplate() { + String name = "TestTemplate"; + String url = "http://example.com/template"; + String checksum = "abc123"; + Storage.ImageFormat format = Storage.ImageFormat.QCOW2; + long guestOsId = 1L; + Long storeId = 100L; + Long templateId = 1L; + String filePath = "/mnt/nfs"; + TemplateDataStoreVO templateDataStoreVO = Mockito.mock(TemplateDataStoreVO.class); + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + doNothing().when(systemVmTemplateRegistration).createCrossZonesTemplateZoneRefEntries(anyLong()); + doNothing().when(systemVmTemplateRegistration).setupTemplateOnStore(anyString(), any(), anyString()); + doNothing().when(systemVmTemplateRegistration).readTemplateProperties(anyString(), any()); + doNothing().when(systemVmTemplateRegistration).updateTemplateDetails(any()); + + Long result = systemVmTemplateRegistration.performTemplateRegistrationOperations(name, templateDetails, url, checksum, format, guestOsId, storeId, templateId, filePath, templateDataStoreVO); + + assertNotNull(result); + assertEquals(templateId, result); + verify(vmTemplateDao, never()).persist(any()); + verify(systemVmTemplateRegistration).createCrossZonesTemplateZoneRefEntries(anyLong()); + verify(systemVmTemplateRegistration, never()).createTemplateStoreRefEntry(any()); + verify(systemVmTemplateRegistration).setupTemplateOnStore(anyString(), any(), anyString()); + verify(systemVmTemplateRegistration).updateTemplateDetails(any()); + } + + @Test + public void performTemplateRegistrationOperations_ThrowsExceptionWhenTemplateCreationFails() { + String name = "TestTemplate"; + String url = "http://example.com/template"; + String checksum = "abc123"; + Storage.ImageFormat format = Storage.ImageFormat.QCOW2; + long guestOsId = 1L; + Long storeId = 100L; + Long templateId = null; + String filePath = "/mnt/nfs"; + TemplateDataStoreVO templateDataStoreVO = null; + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + + when(templateDetails.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM); + when(templateDetails.getArch()).thenReturn(CPU.CPUArch.amd64); + doReturn(null).when(vmTemplateDao).persist(any()); + + assertThrows(CloudRuntimeException.class, () -> { + systemVmTemplateRegistration.performTemplateRegistrationOperations(name, templateDetails, url, checksum, format, guestOsId, storeId, templateId, filePath, templateDataStoreVO); + }); + + verify(vmTemplateDao).persist(any()); + verify(systemVmTemplateRegistration, never()).createCrossZonesTemplateZoneRefEntries(anyLong()); + verify(systemVmTemplateRegistration, never()).createTemplateStoreRefEntry(any()); + verify(systemVmTemplateRegistration, never()).setupTemplateOnStore(anyString(), any(), anyString()); + verify(systemVmTemplateRegistration, never()).updateTemplateDetails(any()); + } + + @Test + public void setupTemplateOnStore_ThrowsExceptionWhenScriptNotFound() { + String templateName = "templateName"; + String destTempFolder = "/tmp/folder"; + SystemVmTemplateRegistration.MetadataTemplateDetails templateDetails = + Mockito.mock(SystemVmTemplateRegistration.MetadataTemplateDetails.class); + + try (MockedStatic - + + @@ -58,7 +109,7 @@ - +
@@ -73,18 +124,18 @@

no
+ title="Move/Drag viewport"> - + + title="Show extra keys">
no
- + title="Full screen"> @@ -255,10 +306,10 @@

no

-

- +
+ - + @@ -273,7 +324,7 @@

no
- +
@@ -288,7 +339,7 @@

no
- +
@@ -298,21 +349,21 @@

no
The server has provided the following identifying information:

- Fingerprint: + Fingerprint:
Please verify that the information is correct and press "Approve". Otherwise press "Reject".
-
- - +
+ +
- +
@@ -326,17 +377,17 @@

no

-
- +
+
- +
- +
diff --git a/systemvm/agent/noVNC/vnc_lite.html b/systemvm/agent/noVNC/vnc_lite.html index 1eea46022224..8e6aaeabdecf 100644 --- a/systemvm/agent/noVNC/vnc_lite.html +++ b/systemvm/agent/noVNC/vnc_lite.html @@ -7,7 +7,7 @@ This is a self-contained file which doesn't import WebUtil or external CSS. - Copyright (C) 2019 The noVNC Authors + Copyright (C) 2019 The noVNC authors noVNC is licensed under the MPL 2.0 (see LICENSE.txt) This file is licensed under the 2-Clause BSD license (see LICENSE.txt). @@ -87,7 +87,7 @@ // When this function is called, the server requires // credentials to authenticate function credentialsAreRequired(e) { - const password = prompt("Password Required:"); + const password = prompt("Password required:"); rfb.sendCredentials({ password: password }); } @@ -119,20 +119,14 @@ // query string. If the variable isn't defined in the URL // it returns the default value instead. function readQueryVariable(name, defaultValue) { - // A URL with a query parameter can look like this (But will most probably get logged on the http server): + // A URL with a query parameter can look like this: // https://www.example.com?myqueryparam=myvalue // - // For privacy (Using a hastag #, the parameters will not be sent to the server) - // the url can be requested in the following way: - // https://www.example.com#myqueryparam=myvalue&password=secreatvalue - // - // Even Mixing public and non public parameters will work: - // https://www.example.com?nonsecretparam=example.com#password=secreatvalue - // // Note that we use location.href instead of location.search // because Firefox < 53 has a bug w.r.t location.search const re = new RegExp('.*[?&]' + name + '=([^&#]*)'), - match = ''.concat(document.location.href, window.location.hash).match(re); + match = document.location.href.match(re); + if (typeof defaultValue === 'undefined') { defaultValue = null; } if (match) { diff --git a/systemvm/agent/scripts/_run.sh b/systemvm/agent/scripts/_run.sh index 11158ecf5bd1..bb024f71c08b 100755 --- a/systemvm/agent/scripts/_run.sh +++ b/systemvm/agent/scripts/_run.sh @@ -60,4 +60,4 @@ if [ "$(uname -m | grep '64')" == "" ]; then fi fi -java -Djavax.net.ssl.trustStore=./certs/realhostip.keystore -Djdk.tls.ephemeralDHKeySize=2048 -Dlog.home=$LOGHOME -mx${maxmem}m -cp $CP com.cloud.agent.AgentShell $keyvalues $@ +java -Djavax.net.ssl.trustStore=./certs/systemvm.keystore -Djdk.tls.ephemeralDHKeySize=2048 -Dlog.home=$LOGHOME -mx${maxmem}m -cp $CP com.cloud.agent.AgentShell $keyvalues $@ diff --git a/systemvm/agent/scripts/config_ssl.sh b/systemvm/agent/scripts/config_ssl.sh index e9340b099f62..3968b2617f21 100755 --- a/systemvm/agent/scripts/config_ssl.sh +++ b/systemvm/agent/scripts/config_ssl.sh @@ -52,13 +52,13 @@ cflag= cpkflag= cpcflag= cccflag= -customPrivKey=$(dirname $0)/certs/realhostip.key -customPrivCert=$(dirname $0)/certs/realhostip.crt +customPrivKey=$(dirname $0)/certs/systemvm.key +customPrivCert=$(dirname $0)/certs/systemvm.crt customCertChain= customCACert= publicIp= hostName= -keyStore=$(dirname $0)/certs/realhostip.keystore +keyStore=$(dirname $0)/certs/systemvm.keystore defaultJavaKeyStoreFile=/etc/ssl/certs/java/cacerts defaultJavaKeyStorePass="changeit" aliasName="CPVMCertificate" diff --git a/systemvm/agent/scripts/run-proxy.sh b/systemvm/agent/scripts/run-proxy.sh old mode 100644 new mode 100755 index f26f54b12b53..40e7c8f64479 --- a/systemvm/agent/scripts/run-proxy.sh +++ b/systemvm/agent/scripts/run-proxy.sh @@ -33,16 +33,4 @@ do CP=${CP}:$file done -#CMDLINE=$(cat /proc/cmdline) -#for i in $CMDLINE -# do -# KEY=$(echo $i | cut -d= -f1) -# VALUE=$(echo $i | cut -d= -f2) -# case $KEY in -# mgmt_host) -# MGMT_HOST=$VALUE -# ;; -# esac -# done - java -mx700m -cp $CP:./conf com.cloud.consoleproxy.ConsoleProxy $@ diff --git a/systemvm/agent/scripts/ssvm-check.sh b/systemvm/agent/scripts/ssvm-check.sh old mode 100644 new mode 100755 index f5d69cb45488..86cecb75719c --- a/systemvm/agent/scripts/ssvm-check.sh +++ b/systemvm/agent/scripts/ssvm-check.sh @@ -41,7 +41,7 @@ isCifs() { # ping dns server echo ================================================ -DNSSERVER=`egrep '^nameserver' /etc/resolv.conf | awk '{print $2}'| head -1` +DNSSERVER=`grep -E '^nameserver' /etc/resolv.conf | awk '{print $2}'| head -1` echo "First DNS server is " $DNSSERVER ping -c 2 $DNSSERVER if [ $? -eq 0 ] diff --git a/systemvm/agent/scripts/utils.sh b/systemvm/agent/scripts/utils.sh old mode 100644 new mode 100755 diff --git a/systemvm/agent/ui/viewer.ftl b/systemvm/agent/ui/viewer.ftl index e6b12071111e..fef0d7e0b68e 100644 --- a/systemvm/agent/ui/viewer.ftl +++ b/systemvm/agent/ui/viewer.ftl @@ -18,10 +18,10 @@ under the License. --> - - - - + + + + ${title} @@ -44,7 +44,7 @@ under the License.
- - Sleep for 3 min to ensure that host is in UP state - - - s - 180 - - - - - - createStoragePool - - - zoneid - 2 - - - podid - 2 - - - name - primary storage - - - url - nfs://192.168.161.21/export/regression/primary - - - clusterid - 1 - - - - - + + + + + addSecondaryStorage + + + zoneid + 2 + + + url + nfs://192.168.161.21/export/regression/secondary + + + + + + addHost + + + zoneid + 2 + + + podid + 2 + + + url + http://192.168.161.25 + + + username + root + + + password + password + + + clustername + firstcluster + + + + + + sleep.sh + + Sleep for 3 min to ensure that host is in UP state + + + s + 180 + + + + + + createStoragePool + + + zoneid + 2 + + + podid + 2 + + + name + primary storage + + + url + nfs://192.168.161.21/export/regression/primary + + + clusterid + 1 + + + + + diff --git a/test/integration/component/maint/test_egress_rules_host_maintenance.py b/test/integration/component/maint/test_egress_rules_host_maintenance.py index 40a3913642fa..820d4d14550a 100644 --- a/test/integration/component/maint/test_egress_rules_host_maintenance.py +++ b/test/integration/component/maint/test_egress_rules_host_maintenance.py @@ -175,19 +175,19 @@ def test_egress_after_host_maintenance(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) diff --git a/test/integration/component/maint/test_primary_storage_nfsmountopts_kvm.py b/test/integration/component/maint/test_primary_storage_nfsmountopts_kvm.py index f2ad18188ca7..21aee0cc7176 100644 --- a/test/integration/component/maint/test_primary_storage_nfsmountopts_kvm.py +++ b/test/integration/component/maint/test_primary_storage_nfsmountopts_kvm.py @@ -110,7 +110,7 @@ def getUnusedNFSVersions(self, filter): def getNFSMountOptionForPool(self, option, poolId): nfsstat_cmd = "nfsstat -m | sed -n '/%s/{ n; p }'" % poolId nfsstat = self.sshClient.execute(nfsstat_cmd) - if (nfsstat == None): + if nfsstat == None or len(nfsstat) == 0: return None stat = nfsstat[0] vers = stat[stat.find(option):].split("=")[1].split(",")[0] diff --git a/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py b/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py index 212320c2c2f1..4a12345b6e80 100644 --- a/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py +++ b/test/integration/component/test_acl_sharednetwork_deployVM-impersonation.py @@ -344,7 +344,7 @@ def tearDown(cls): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_all_domainuser(self): """ - Valiate that ROOT admin is able to deploy a VM for other users in a shared network with scope=all + Validate that ROOT admin is able to deploy a VM for other users in a shared network with scope=all """ # Deploy VM for a user in a domain under ROOT as admin @@ -372,7 +372,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_all_domainuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_all_domainadminuser(self): """ - Valiate that ROOT admin is able to deploy a VM for a domain admin users in a shared network with scope=all + Validate that ROOT admin is able to deploy a VM for a domain admin users in a shared network with scope=all """ # Deploy VM for an admin user in a domain under ROOT as admin @@ -400,7 +400,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_all_domainadminuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_all_subdomainuser(self): """ - Valiate that ROOT admin is able to deploy a VM for any user in a subdomain in a shared network with scope=all + Validate that ROOT admin is able to deploy a VM for any user in a subdomain in a shared network with scope=all """ # Deploy VM as user in a subdomain under ROOT @@ -426,7 +426,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_all_subdomainuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_all_subdomainadminuser(self): """ - Valiate that ROOT admin is able to deploy a VM for admin user in a domain in a shared network with scope=all + Validate that ROOT admin is able to deploy a VM for admin user in a domain in a shared network with scope=all """ # Deploy VM as an admin user in a subdomain under ROOT @@ -453,7 +453,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_all_subdomainadminuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_all_ROOTuser(self): """ - Valiate that ROOT admin is able to deploy a VM for user in ROOT domain in a shared network with scope=all + Validate that ROOT admin is able to deploy a VM for user in ROOT domain in a shared network with scope=all """ # Deploy VM as user in ROOT domain @@ -482,7 +482,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_all_ROOTuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_domainuser(self): """ - Valiate that ROOT admin is able to deploy a VM for domain user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is able to deploy a VM for domain user in a shared network with scope=domain with no subdomain access """ # Deploy VM as user in a domain that has shared network with no subdomain access @@ -510,7 +510,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_domai @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_domainadminuser(self): """ - Valiate that ROOT admin is able to deploy a VM for domain admin user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is able to deploy a VM for domain admin user in a shared network with scope=domain with no subdomain access """ # Deploy VM as an admin user in a domain that has shared network with no subdomain access @@ -538,7 +538,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_domai @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_subdomainuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for sub domain user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is NOT able to deploy a VM for sub domain user in a shared network with scope=domain with no subdomain access """ # Deploy VM as user in a subdomain under a domain that has shared network with no subdomain access @@ -569,7 +569,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_subdo @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_subdomainadminuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for sub domain admin user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is NOT able to deploy a VM for sub domain admin user in a shared network with scope=domain with no subdomain access """ # Deploy VM as an admin user in a subdomain under a domain that has shared network with no subdomain access @@ -599,7 +599,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_subdo @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_parentdomainuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for parent domain user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is NOT able to deploy a VM for parent domain user in a shared network with scope=domain with no subdomain access """ # Deploy VM as user in parentdomain of a domain that has shared network with no subdomain access @@ -629,7 +629,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_paren @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_parentdomainadminuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=domain with no subdomain access """ # Deploy VM as an admin user in parentdomain of a domain that has shared network with no subdomain access @@ -659,7 +659,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_paren @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_ROOTuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=domain with no subdomain access + Validate that ROOT admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=domain with no subdomain access """ # Deploy VM as user in ROOT domain @@ -691,7 +691,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_nosubdomainaccess_ROOTu @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_domainuser(self): """ - Valiate that ROOT admin is able to deploy a VM for domain user in a shared network with scope=domain with subdomain access + Validate that ROOT admin is able to deploy a VM for domain user in a shared network with scope=domain with subdomain access """ # Deploy VM as user in a domain that has shared network with subdomain access @@ -719,7 +719,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_dom @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_domainadminuser(self): """ - Valiate that ROOT admin is able to deploy a VM for domain admin user in a shared network with scope=domain with subdomain access + Validate that ROOT admin is able to deploy a VM for domain admin user in a shared network with scope=domain with subdomain access """ # Deploy VM as an admin user in a domain that has shared network with subdomain access @@ -747,7 +747,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_dom @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_subdomainuser(self): """ - Valiate that ROOT admin is able to deploy a VM for subdomain user in a shared network with scope=domain with subdomain access + Validate that ROOT admin is able to deploy a VM for subdomain user in a shared network with scope=domain with subdomain access """ # Deploy VM as user in a subdomain under a domain that has shared network with subdomain access @@ -774,7 +774,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_sub @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_subdomainadminuser(self): """ - Valiate that ROOT admin is able to deploy a VM for subdomain admin user in a shared network with scope=domain with subdomain access + Validate that ROOT admin is able to deploy a VM for subdomain admin user in a shared network with scope=domain with subdomain access """ # Deploy VM as an admin user in a subdomain under a domain that has shared network with subdomain access @@ -801,7 +801,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_sub @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_parentdomainuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for parent domain user in a shared network with scope=domain with subdomain access + Validate that ROOT admin is NOT able to deploy a VM for parent domain user in a shared network with scope=domain with subdomain access """ # Deploy VM as user in parentdomain of a domain that has shared network with subdomain access @@ -831,7 +831,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_par @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_parentdomainadminuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=domain with subdomain access + Validate that ROOT admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=domain with subdomain access """ # Deploy VM as an admin user in parentdomain of a domain that has shared network with subdomain access @@ -861,7 +861,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_par @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_ROOTuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=domain with subdomain access + Validate that ROOT admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=domain with subdomain access """ # Deploy VM as user in ROOT domain @@ -893,7 +893,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_domain_withsubdomainaccess_ROO @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_account_domainuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for user in the same domain but in a different account in a shared network with scope=account + Validate that ROOT admin is NOT able to deploy a VM for user in the same domain but in a different account in a shared network with scope=account """ # Deploy VM as user in a domain under the same domain but different account from the account that has a shared network with scope=account @@ -923,7 +923,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_account_domainuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_account_domainadminuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for admin user in the same domain but in a different account in a shared network with scope=account + Validate that ROOT admin is NOT able to deploy a VM for admin user in the same domain but in a different account in a shared network with scope=account """ # Deploy VM as admin user for a domain that has an account with shared network with scope=account @@ -953,7 +953,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_account_domainadminuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_account_user(self): """ - Valiate that ROOT admin is able to deploy a VM for regular user in a shared network with scope=account + Validate that ROOT admin is able to deploy a VM for regular user in a shared network with scope=account """ # Deploy VM as account with shared network with scope=account @@ -981,7 +981,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_account_user(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_account_differentdomain(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for a admin user in a shared network with scope=account which the admin user does not have access to + Validate that ROOT admin is NOT able to deploy a VM for a admin user in a shared network with scope=account which the admin user does not have access to """ # Deploy VM as an admin user in a subdomain under ROOT @@ -1011,7 +1011,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_account_differentdomain(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_admin_scope_account_ROOTuser(self): """ - Valiate that ROOT admin is NOT able to deploy a VM for a user in ROOT domain in a shared network with scope=account which the user does not have access to + Validate that ROOT admin is NOT able to deploy a VM for a user in ROOT domain in a shared network with scope=account which the user does not have access to """ # Deploy VM as user in ROOT domain @@ -1043,7 +1043,7 @@ def test_deployVM_in_sharedNetwork_as_admin_scope_account_ROOTuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_domainuser(self): """ - Valiate that Domain admin is able to deploy a VM for a domain user in a shared network with scope=all + Validate that Domain admin is able to deploy a VM for a domain user in a shared network with scope=all """ # Deploy VM for a user in a domain under ROOT as admin @@ -1070,7 +1070,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_domainuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_domainadminuser(self): """ - Valiate that Domain admin is able to deploy a VM for a domain admin user in a shared network with scope=all + Validate that Domain admin is able to deploy a VM for a domain admin user in a shared network with scope=all """ # Deploy VM for an admin user in a domain under ROOT as admin @@ -1097,7 +1097,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_domainadminuser(self @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_subdomainuser(self): """ - Valiate that Domain admin is able to deploy a VM for a sub domain user in a shared network with scope=all + Validate that Domain admin is able to deploy a VM for a sub domain user in a shared network with scope=all """ # Deploy VM as user in a subdomain under ROOT @@ -1123,7 +1123,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_subdomainuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_subdomainadminuser(self): """ - Valiate that Domain admin is able to deploy a VM for a sub domain admin user in a shared network with scope=all + Validate that Domain admin is able to deploy a VM for a sub domain admin user in a shared network with scope=all """ # Deploy VM as an admin user in a subdomain under ROOT @@ -1149,7 +1149,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_subdomainadminuser(s @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_ROOTuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=all + Validate that Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=all """ # Deploy VM as user in ROOT domain @@ -1177,7 +1177,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_ROOTuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_crossdomainuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for user in other domain in a shared network with scope=all + Validate that Domain admin is NOT able to deploy a VM for user in other domain in a shared network with scope=all """ # Deploy VM as user in ROOT domain @@ -1208,7 +1208,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_all_crossdomainuser(self @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_domainuser(self): """ - Valiate that Domain admin is able to deploy a VM for domain user in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is able to deploy a VM for domain user in a shared network with scope=Domain and no subdomain access """ # Deploy VM as user in a domain that has shared network with no subdomain access @@ -1235,7 +1235,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_domainadminuser(self): """ - Valiate that Domain admin is able to deploy a VM for domain admin user in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is able to deploy a VM for domain admin user in a shared network with scope=Domain and no subdomain access """ # Deploy VM as an admin user in a domain that has shared network with no subdomain access @@ -1263,7 +1263,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_subdomainuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for sub domain user in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is NOT able to deploy a VM for sub domain user in a shared network with scope=Domain and no subdomain access """ # Deploy VM as user in a subdomain under a domain that has shared network with no subdomain access @@ -1293,7 +1293,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_subdomainadminuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for sub domain admin user in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is NOT able to deploy a VM for sub domain admin user in a shared network with scope=Domain and no subdomain access """ # Deploy VM as an admin user in a subdomain under a domain that has shared network with no subdomain access @@ -1323,7 +1323,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_parentdomainuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for parent domain user in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is NOT able to deploy a VM for parent domain user in a shared network with scope=Domain and no subdomain access """ # Deploy VM as user in parentdomain of a domain that has shared network with no subdomain access @@ -1353,7 +1353,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_parentdomainadminuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is NOT able to deploy a VM for parent domain admin user in a shared network with scope=Domain and no subdomain access """ # Deploy VM as an admin user in parentdomain of a domain that has shared network with no subdomain access @@ -1383,7 +1383,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess_ROOTuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=Domain and no subdomain access + Validate that Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=Domain and no subdomain access """ # Deploy VM as user in ROOT domain @@ -1414,7 +1414,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_nosubdomainaccess @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_domainuser(self): """ - Valiate that Domain admin is able to deploy a VM for regular user in domain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is able to deploy a VM for regular user in domain in a shared network with scope=Domain and subdomain access """ # Deploy VM as user in a domain that has shared network with subdomain access @@ -1441,7 +1441,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_domainadminuser(self): """ - Valiate that Domain admin is able to deploy a VM for admin user in domain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is able to deploy a VM for admin user in domain in a shared network with scope=Domain and subdomain access """ # Deploy VM as an admin user in a domain that has shared network with subdomain access @@ -1468,7 +1468,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_subdomainuser(self): """ - Valiate that Domain admin is able to deploy a VM for regular user in subdomain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is able to deploy a VM for regular user in subdomain in a shared network with scope=Domain and subdomain access """ # Deploy VM as user in a subdomain under a domain that has shared network with subdomain access @@ -1494,7 +1494,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_subdomainadminuser(self): """ - Valiate that Domain admin is able to deploy a VM for admin user in subdomain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is able to deploy a VM for admin user in subdomain in a shared network with scope=Domain and subdomain access """ # Deploy VM as an admin user in a subdomain under a domain that has shared network with subdomain access @@ -1520,7 +1520,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_parentdomainuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for regular user in parent domain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is NOT able to deploy a VM for regular user in parent domain in a shared network with scope=Domain and subdomain access """ # Deploy VM as user in parentdomain of a domain that has shared network with subdomain access @@ -1549,7 +1549,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_parentdomainadminuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for admin user in parent domain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is NOT able to deploy a VM for admin user in parent domain in a shared network with scope=Domain and subdomain access """ # Deploy VM as an admin user in parentdomain of a domain that has shared network with subdomain access @@ -1579,7 +1579,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainaccess_ROOTuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=Domain and subdomain access + Validate that Domain admin is NOT able to deploy a VM for user in ROOT domain in a shared network with scope=Domain and subdomain access """ # Deploy VM as user in ROOT domain @@ -1610,7 +1610,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_domain_withsubdomainacce @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_domainuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for user in the same domain but belonging to a different account in a shared network with scope=account + Validate that Domain admin is NOT able to deploy a VM for user in the same domain but belonging to a different account in a shared network with scope=account """ # Deploy VM as user in a domain under the same domain but different account from the account that has a shared network with scope=account @@ -1639,7 +1639,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_domainuser(self) @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_domainadminuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for an admin user in the same domain but belonging to a different account in a shared network with scope=account + Validate that Domain admin is NOT able to deploy a VM for an admin user in the same domain but belonging to a different account in a shared network with scope=account """ # Deploy VM as admin user for a domain that has an account with shared network with scope=account @@ -1668,7 +1668,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_domainadminuser( @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_user(self): """ - Valiate that Domain admin is able to deploy a VM for an regular user in a shared network with scope=account + Validate that Domain admin is able to deploy a VM for an regular user in a shared network with scope=account """ # Deploy VM as account with shared network with scope=account @@ -1695,7 +1695,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_user(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_differentdomain(self): """ - Valiate that Domain admin is able NOT able to deploy a VM for an regular user from a differnt domain in a shared network with scope=account + Validate that Domain admin is able NOT able to deploy a VM for an regular user from a differnt domain in a shared network with scope=account """ # Deploy VM as an admin user in a subdomain under ROOT @@ -1724,7 +1724,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_differentdomain( @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_ROOTuser(self): """ - Valiate that Domain admin is NOT able to deploy a VM for an regular user in ROOT domain in a shared network with scope=account + Validate that Domain admin is NOT able to deploy a VM for an regular user in ROOT domain in a shared network with scope=account """ # Deploy VM as user in ROOT domain @@ -1754,7 +1754,7 @@ def test_deployVM_in_sharedNetwork_as_domainadmin_scope_account_ROOTuser(self): @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_regularuser_scope_all_anotherusersamedomain(self): """ - Valiate that regular user is able NOT able to deploy a VM for another user in the same domain in a shared network with scope=all + Validate that regular user is able NOT able to deploy a VM for another user in the same domain in a shared network with scope=all """ # Deploy VM for a user in a domain under ROOT as admin @@ -1782,7 +1782,7 @@ def test_deployVM_in_sharedNetwork_as_regularuser_scope_all_anotherusersamedomai @attr("simulator_only", tags=["advanced"], required_hardware="false") def test_deployVM_in_sharedNetwork_as_regularuser_scope_all_crossdomain(self): """ - Valiate that regular user is able NOT able to deploy a VM for another user in a different domain in a shared network with scope=all + Validate that regular user is able NOT able to deploy a VM for another user in a different domain in a shared network with scope=all """ # Deploy VM for a user in a domain under ROOT as admin diff --git a/test/integration/component/test_base_image_updation.py b/test/integration/component/test_base_image_updation.py index 2a0a4e61c255..9398f883c2d8 100644 --- a/test/integration/component/test_base_image_updation.py +++ b/test/integration/component/test_base_image_updation.py @@ -575,7 +575,7 @@ def test_04_reoccuring_snapshot_rules(self): ) sleep_seconds = (self.services["recurring_snapshot"]["schedule"]) * 3600 + 600 sleep_minutes = sleep_seconds/60 - self.debug("Sleeping for %s minutes till the volume is snapshoted" %sleep_minutes) + self.debug("Sleeping for %s minutes till the volume is snapshotted" %sleep_minutes) time.sleep(sleep_seconds) retriesCount = self.services["retriesCount"] diff --git a/test/integration/component/test_blocker_bugs.py b/test/integration/component/test_blocker_bugs.py index 7b497cfe2949..e393c7bed02a 100644 --- a/test/integration/component/test_blocker_bugs.py +++ b/test/integration/component/test_blocker_bugs.py @@ -542,7 +542,7 @@ def test_01_list_routers_admin(self): # Validate the following - # 1. PreReq: have rounters that are owned by other account + # 1. PreReq: have routers that are owned by other account # 2. Create domain and create accounts in that domain # 3. Create one VM for each account # 4. Using Admin , run listRouters. It should return all the routers diff --git a/test/integration/component/test_deploy_vm_lease.py b/test/integration/component/test_deploy_vm_lease.py new file mode 100644 index 000000000000..4fe084841966 --- /dev/null +++ b/test/integration/component/test_deploy_vm_lease.py @@ -0,0 +1,358 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Import Local Modules +from nose.plugins.attrib import attr +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import cleanup_resources +from marvin.lib.base import (Account, + VirtualMachine, + ServiceOffering, + DiskOffering, + Configurations) +from marvin.lib.common import (get_zone, + get_domain, + get_test_template, + is_config_suitable) + + +class TestDeployVMLease(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + cls.testClient = super(TestDeployVMLease, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + cls.testdata = cls.testClient.getParsedTestDataConfig() + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client) + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.hypervisor = cls.testClient.getHypervisorInfo() + + cls.template = get_test_template( + cls.api_client, + cls.zone.id, + cls.hypervisor + ) + + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + + # enable instance lease feature + Configurations.update(cls.api_client, + name="instance.lease.enabled", + value="true" + ) + + # Create service, disk offerings etc + cls.non_lease_svc_offering = ServiceOffering.create( + cls.api_client, + cls.testdata["service_offering"], + name="non-lease-svc-offering" + ) + + # Create service, disk offerings etc + cls.lease_svc_offering = ServiceOffering.create( + cls.api_client, + cls.testdata["service_offering"], + name="lease-svc-offering", + leaseduration=20, + leaseexpiryaction="DESTROY" + ) + + cls.disk_offering = DiskOffering.create( + cls.api_client, + cls.testdata["disk_offering"] + ) + + cls._cleanup = [ + cls.lease_svc_offering, + cls.non_lease_svc_offering, + cls.disk_offering + ] + return + + @classmethod + def tearDownClass(cls): + try: + # disable instance lease feature + Configurations.update(cls.api_client, + name="instance.lease.enabled", + value="false" + ) + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.hypervisor = self.testClient.getHypervisorInfo() + self.testdata["virtual_machine"]["zoneid"] = self.zone.id + self.testdata["virtual_machine"]["template"] = self.template.id + self.testdata["iso"]["zoneid"] = self.zone.id + self.account = Account.create( + self.apiclient, + self.testdata["account"], + domainid=self.domain.id + ) + self.cleanup = [self.account] + return + + def tearDown(self): + try: + self.debug("Cleaning up the resources") + cleanup_resources(self.apiclient, self.cleanup) + self.debug("Cleanup complete!") + except Exception as e: + self.debug("Warning! Exception in tearDown: %s" % e) + + @attr( + tags=[ + "advanced", + "basic"], + required_hardware="true") + def test_01_deploy_vm_no_lease_svc_offering(self): + """Test Deploy Virtual Machine from non-lease-svc-offering + + Validate the following: + 1. deploy VM using non-lease-svc-offering + 2. confirm vm has no lease configured + """ + + non_lease_vm = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + templateid=self.template.id, + serviceofferingid=self.non_lease_svc_offering.id, + diskofferingid=self.disk_offering.id, + hypervisor=self.hypervisor + ) + self.verify_no_lease_configured_for_vm(non_lease_vm.id) + return + + @attr( + tags=[ + "advanced", + "basic"], + required_hardware="true") + def test_02_deploy_vm_no_lease_svc_offering_with_lease_params(self): + """Test Deploy Virtual Machine from non-lease-svc-offering and lease parameters are used to enabled lease for vm + + Validate the following: + 1. deploy VM using non-lease-svc-offering and passing leaseduration and leaseexpiryaction + 2. confirm vm has lease configured + """ + lease_vm = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + templateid=self.template.id, + serviceofferingid=self.non_lease_svc_offering.id, + diskofferingid=self.disk_offering.id, + hypervisor=self.hypervisor, + leaseduration=10, + leaseexpiryaction="STOP" + ) + self.verify_lease_configured_for_vm(lease_vm.id, lease_duration=10, lease_expiry_action="STOP") + return + + @attr( + tags=[ + "advanced", + "basic"], + required_hardware="true") + def test_03_deploy_vm_lease_svc_offering_with_no_param(self): + """Test Deploy Virtual Machine from lease-svc-offering without lease params + expect vm to inherit svc_offering lease properties + + Validate the following: + 1. deploy VM using lease-svc-offering without passing leaseduration and leaseexpiryaction + 2. confirm vm has lease configured + """ + lease_vm = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + templateid=self.template.id, + serviceofferingid=self.lease_svc_offering.id, + diskofferingid=self.disk_offering.id, + hypervisor=self.hypervisor + ) + self.verify_lease_configured_for_vm(lease_vm.id, lease_duration=20, lease_expiry_action="DESTROY") + return + + @attr( + tags=[ + "advanced", + "basic"], + required_hardware="true") + def test_04_deploy_vm_lease_svc_offering_with_param(self): + """Test Deploy Virtual Machine from lease-svc-offering with overridden lease properties + + Validate the following: + 1. confirm svc_offering has lease properties + 2. deploy VM using lease-svc-offering and leaseduration and leaseexpiryaction passed + 3. confirm vm has lease configured + """ + self.verify_svc_offering() + + lease_vm = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + templateid=self.template.id, + serviceofferingid=self.lease_svc_offering.id, + diskofferingid=self.disk_offering.id, + hypervisor=self.hypervisor, + leaseduration=30, + leaseexpiryaction="STOP" + ) + self.verify_lease_configured_for_vm(lease_vm.id, lease_duration=30, lease_expiry_action="STOP") + return + + + @attr( + tags=[ + "advanced", + "basic"], + required_hardware="true") + def test_05_deploy_vm_lease_svc_offering_with_lease_param_disabled(self): + """Test Deploy Virtual Machine from lease-svc-offering and passing -1 leaseduration to set no-expiry + + Validate the following: + 1. deploy VM using lease-svc-offering + 2. leaseduration is set as -1 in the deploy vm request to disable lease + 3. confirm vm has no lease configured + """ + + lease_vm = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + templateid=self.template.id, + serviceofferingid=self.non_lease_svc_offering.id, + diskofferingid=self.disk_offering.id, + hypervisor=self.hypervisor, + leaseduration=-1 + ) + + vms = VirtualMachine.list( + self.apiclient, + id=lease_vm.id + ) + vm = vms[0] + self.verify_no_lease_configured_for_vm(vm.id) + return + + @attr( + tags=[ + "advanced", + "basic"], + required_hardware="true") + def test_06_deploy_vm_lease_svc_offering_with_disabled_lease(self): + """Test Deploy Virtual Machine from lease-svc-offering with lease feature disabled + + Validate the following: + 1. Disable lease feature + 2. deploy VM using lease-svc-offering + 3. confirm vm has no lease configured + """ + + Configurations.update(self.api_client, + name="instance.lease.enabled", + value="false" + ) + + lease_vm = VirtualMachine.create( + self.apiclient, + self.testdata["virtual_machine"], + accountid=self.account.name, + domainid=self.account.domainid, + templateid=self.template.id, + serviceofferingid=self.lease_svc_offering.id, + diskofferingid=self.disk_offering.id, + hypervisor=self.hypervisor + ) + + vms = VirtualMachine.list( + self.apiclient, + id=lease_vm.id + ) + vm = vms[0] + self.verify_no_lease_configured_for_vm(vm.id) + return + + + def verify_svc_offering(self): + svc_offering_list = ServiceOffering.list( + self.api_client, + id=self.lease_svc_offering.id + ) + + svc_offering = svc_offering_list[0] + + self.assertIsNotNone( + svc_offering.leaseduration, + "svc_offering has lease configured" + ) + + self.assertEqual( + 20, + svc_offering.leaseduration, + "svc_offering has 20 days for lease" + ) + + def verify_lease_configured_for_vm(self, vm_id=None, lease_duration=None, lease_expiry_action=None): + vms = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + vm = vms[0] + self.assertEqual( + lease_duration, + vm.leaseduration, + "check to confirm leaseduration is configured" + ) + + self.assertEqual( + lease_expiry_action, + vm.leaseexpiryaction, + "check to confirm leaseexpiryaction is configured" + ) + + self.assertIsNotNone(vm.leaseexpirydate, "confirm leaseexpirydate is available") + + + def verify_no_lease_configured_for_vm(self, vm_id=None): + if vm_id == None: + return + vms = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + vm = vms[0] + self.assertIsNone(vm.leaseduration) + self.assertIsNone(vm.leaseexpiryaction) diff --git a/test/integration/component/test_egress_rules.py b/test/integration/component/test_egress_rules.py index c1d12d11c015..014303fc1f07 100644 --- a/test/integration/component/test_egress_rules.py +++ b/test/integration/component/test_egress_rules.py @@ -375,19 +375,19 @@ def test_authorizeIngressRule(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -533,19 +533,19 @@ def test_01_default_group_with_egress(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -730,19 +730,19 @@ def test_01_default_group_with_egress(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -910,19 +910,19 @@ def test_revoke_egress_rule(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1165,8 +1165,8 @@ def tearDownClass(cls): return @attr(tags = ["sg", "eip", "advancedsg"]) - def test_invalid_account_authroize(self): - """Test invalid account authroize + def test_invalid_account_authorize(self): + """Test invalid account authorize """ @@ -1188,19 +1188,19 @@ def test_invalid_account_authroize(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1320,19 +1320,19 @@ def test_multiple_account_egress_rule_negative(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.accountA.name, domainid=self.accountA.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1568,19 +1568,19 @@ def test_multiple_account_egress_rule_positive(self): self.debug("Created security group with ID: %s" % security_groupA.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.accountA.name, domainid=self.accountA.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1594,19 +1594,19 @@ def test_multiple_account_egress_rule_positive(self): self.debug("Created security group with ID: %s" % security_groupB.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.accountB.name, domainid=self.accountB.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1847,19 +1847,19 @@ def test_start_stop_vm_egress(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -2037,19 +2037,19 @@ def test_invalid_parameters(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) 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 fbcff2a5fd0b..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, @@ -4341,7 +4338,7 @@ def test_19_create_update_autoscalevmgroup(self): vmgroup_disabled, "Failed to disable Autoscale VM group" ) - # Verifyign the state of the VM Group afte renabling + # Verifying the state of the VM Group after renabling self.assertEqual( "disabled", vmgroup_disabled.state, @@ -4375,7 +4372,7 @@ def test_19_create_update_autoscalevmgroup(self): vmgroup_enabled, "Failed to enable Autoscale VM group" ) - # Verifyign the state of the VM Group afte renabling + # Verifying the state of the VM Group after renabling self.assertEqual( "enabled", vmgroup_enabled.state, diff --git a/test/integration/component/test_kubernetes_cluster_affinity_groups.py b/test/integration/component/test_kubernetes_cluster_affinity_groups.py new file mode 100644 index 000000000000..80450d37bb2a --- /dev/null +++ b/test/integration/component/test_kubernetes_cluster_affinity_groups.py @@ -0,0 +1,931 @@ +# 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. +"""Tests for Kubernetes cluster affinity groups feature""" + +import unittest +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (listInfrastructure, + listKubernetesSupportedVersions, + addKubernetesSupportedVersion, + deleteKubernetesSupportedVersion, + listKubernetesClusters, + createKubernetesCluster, + stopKubernetesCluster, + startKubernetesCluster, + deleteKubernetesCluster, + scaleKubernetesCluster, + destroyVirtualMachine, + deleteNetwork) +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.base import (ServiceOffering, + Account, + AffinityGroup, + Configurations) +from marvin.lib.utils import (cleanup_resources, + random_gen) +from marvin.lib.common import (get_zone, + get_domain) +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr +from marvin.lib.decoratorGenerators import skipTestIf + +import time + +_multiprocess_shared_ = True + +RAND_SUFFIX = random_gen() + + +class TestKubernetesClusterAffinityGroups(cloudstackTestCase): + """ + Tests for CKS Affinity Groups feature (since 4.23.0) + + This feature allows specifying different affinity groups for each + Kubernetes node type (CONTROL, WORKER, ETCD). + """ + + @classmethod + def setUpClass(cls): + testClient = super(TestKubernetesClusterAffinityGroups, cls).getClsTestClient() + if testClient is None: + raise unittest.SkipTest("Marvin test client not available - check marvin configuration") + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + + cls.hypervisorNotSupported = False + if cls.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]: + cls.hypervisorNotSupported = True + + cls.setup_failed = False + cls._cleanup = [] + cls.kubernetes_version_ids = [] + cls.initial_configuration_cks_enabled = None + + cls.k8s_version = cls.services.get("cks_kubernetes_version_upgrade_to", + cls.services.get("cks_kubernetes_version_upgrade_from")) + + if cls.hypervisorNotSupported == False: + cls.endpoint_url = Configurations.list(cls.apiclient, name="endpoint.url")[0].value + if "localhost" in cls.endpoint_url: + endpoint_url = "http://%s:%d/client/api" % (cls.mgtSvrDetails["mgtSvrIp"], cls.mgtSvrDetails["port"]) + cls.debug("Setting endpoint.url to %s" % endpoint_url) + Configurations.update(cls.apiclient, "endpoint.url", endpoint_url) + + cls.initial_configuration_cks_enabled = Configurations.list( + cls.apiclient, name="cloud.kubernetes.service.enabled")[0].value + if cls.initial_configuration_cks_enabled not in ["true", True]: + cls.debug("Enabling CloudStack Kubernetes Service plugin and restarting management server") + Configurations.update(cls.apiclient, "cloud.kubernetes.service.enabled", "true") + cls.restartServer() + + cls.cks_service_offering = None + + if cls.setup_failed == False: + try: + cls.kubernetes_version = cls.addKubernetesSupportedVersion( + cls.services["cks_kubernetes_versions"][cls.k8s_version]) + cls.kubernetes_version_ids.append(cls.kubernetes_version.id) + except Exception as e: + cls.setup_failed = True + cls.debug("Failed to get Kubernetes version ISO in ready state: %s" % e) + + if cls.setup_failed == False: + cks_offering_data = cls.services["cks_service_offering"] + cks_offering_data["name"] = 'CKS-Instance-' + random_gen() + cls.cks_service_offering = ServiceOffering.create( + cls.apiclient, + cks_offering_data + ) + cls._cleanup.append(cls.cks_service_offering) + + cls.domain = get_domain(cls.apiclient) + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + cls.default_network = None + + return + + @classmethod + def tearDownClass(cls): + # Delete added Kubernetes supported version + for version_id in cls.kubernetes_version_ids: + try: + cls.deleteKubernetesSupportedVersion(version_id) + except Exception as e: + cls.debug("Error during cleanup for Kubernetes versions: %s" % e) + + # Restore CKS enabled + if cls.initial_configuration_cks_enabled not in ["true", True]: + cls.debug("Restoring Kubernetes Service enabled value") + Configurations.update(cls.apiclient, "cloud.kubernetes.service.enabled", "false") + cls.restartServer() + + super(TestKubernetesClusterAffinityGroups, cls).tearDownClass() + + @classmethod + def restartServer(cls): + """Restart management server""" + cls.debug("Restarting management server") + sshClient = SshClient( + cls.mgtSvrDetails["mgtSvrIp"], + 22, + cls.mgtSvrDetails["user"], + cls.mgtSvrDetails["passwd"] + ) + command = "service cloudstack-management stop" + sshClient.execute(command) + + command = "service cloudstack-management start" + sshClient.execute(command) + + # Wait for management to come up in 5 mins + timeout = time.time() + 300 + while time.time() < timeout: + if cls.isManagementUp() is True: + return + time.sleep(5) + cls.setup_failed = True + cls.debug("Management server did not come up, failing") + return + + @classmethod + def isManagementUp(cls): + try: + cls.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd()) + return True + except Exception: + return False + + @classmethod + def waitForKubernetesSupportedVersionIsoReadyState(cls, version_id, retries=30, interval=60): + """Check if Kubernetes supported version ISO is in Ready state""" + while retries > 0: + time.sleep(interval) + list_versions_response = cls.listKubernetesSupportedVersion(version_id) + if not hasattr(list_versions_response, 'isostate') or not list_versions_response or not list_versions_response.isostate: + retries = retries - 1 + continue + if 'Ready' == list_versions_response.isostate: + return + elif 'Failed' == list_versions_response.isostate: + raise Exception("Failed to download template: status - %s" % list_versions_response.isostate) + retries = retries - 1 + raise Exception("Kubernetes supported version Ready state timed out") + + @classmethod + def listKubernetesSupportedVersion(cls, version_id): + listKubernetesSupportedVersionsCmd = listKubernetesSupportedVersions.listKubernetesSupportedVersionsCmd() + listKubernetesSupportedVersionsCmd.id = version_id + versionResponse = cls.apiclient.listKubernetesSupportedVersions(listKubernetesSupportedVersionsCmd) + return versionResponse[0] + + @classmethod + def addKubernetesSupportedVersion(cls, version_service): + addKubernetesSupportedVersionCmd = addKubernetesSupportedVersion.addKubernetesSupportedVersionCmd() + addKubernetesSupportedVersionCmd.semanticversion = version_service["semanticversion"] + addKubernetesSupportedVersionCmd.name = 'v' + version_service["semanticversion"] + '-' + random_gen() + addKubernetesSupportedVersionCmd.url = version_service["url"] + addKubernetesSupportedVersionCmd.mincpunumber = version_service["mincpunumber"] + addKubernetesSupportedVersionCmd.minmemory = version_service["minmemory"] + kubernetes_version = cls.apiclient.addKubernetesSupportedVersion(addKubernetesSupportedVersionCmd) + cls.debug("Waiting for Kubernetes version with ID %s to be ready" % kubernetes_version.id) + cls.waitForKubernetesSupportedVersionIsoReadyState(kubernetes_version.id) + kubernetes_version = cls.listKubernetesSupportedVersion(kubernetes_version.id) + return kubernetes_version + + @classmethod + def deleteKubernetesSupportedVersion(cls, version_id): + deleteKubernetesSupportedVersionCmd = deleteKubernetesSupportedVersion.deleteKubernetesSupportedVersionCmd() + deleteKubernetesSupportedVersionCmd.id = version_id + cls.apiclient.deleteKubernetesSupportedVersion(deleteKubernetesSupportedVersionCmd) + + @classmethod + def listKubernetesCluster(cls, cluster_id=None, cluster_name=None): + listKubernetesClustersCmd = listKubernetesClusters.listKubernetesClustersCmd() + listKubernetesClustersCmd.listall = True + if cluster_id is not None: + listKubernetesClustersCmd.id = cluster_id + if cluster_name is not None: + listKubernetesClustersCmd.name = cluster_name + clusterResponse = cls.apiclient.listKubernetesClusters(listKubernetesClustersCmd) + if (cluster_id is not None or cluster_name is not None) and clusterResponse is not None: + return clusterResponse[0] + return clusterResponse + + @classmethod + def deleteKubernetesCluster(cls, cluster_id): + deleteKubernetesClusterCmd = deleteKubernetesCluster.deleteKubernetesClusterCmd() + deleteKubernetesClusterCmd.id = cluster_id + response = cls.apiclient.deleteKubernetesCluster(deleteKubernetesClusterCmd) + return response + + @classmethod + def stopKubernetesCluster(cls, cluster_id): + stopKubernetesClusterCmd = stopKubernetesCluster.stopKubernetesClusterCmd() + stopKubernetesClusterCmd.id = cluster_id + response = cls.apiclient.stopKubernetesCluster(stopKubernetesClusterCmd) + return response + + def setUp(self): + self.services = self.testClient.getParsedTestDataConfig() + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + self.aff_grp = [] + return + + def tearDown(self): + super(TestKubernetesClusterAffinityGroups, self).tearDown() + + def deleteKubernetesClusterAndVerify(self, cluster_id, verify=True, forced=False): + """Delete Kubernetes cluster and check if it is really deleted""" + delete_response = {} + forceDeleted = False + try: + delete_response = self.deleteKubernetesCluster(cluster_id) + except Exception as e: + if forced: + cluster = self.listKubernetesCluster(cluster_id) + if cluster is not None: + if cluster.state in ['Starting', 'Running', 'Upgrading', 'Scaling']: + self.stopKubernetesCluster(cluster_id) + self.deleteKubernetesCluster(cluster_id) + else: + forceDeleted = True + for cluster_vm in cluster.virtualmachines: + cmd = destroyVirtualMachine.destroyVirtualMachineCmd() + cmd.id = cluster_vm.id + cmd.expunge = True + self.apiclient.destroyVirtualMachine(cmd) + cmd = deleteNetwork.deleteNetworkCmd() + cmd.id = cluster.networkid + cmd.forced = True + self.apiclient.deleteNetwork(cmd) + self.dbclient.execute( + "update kubernetes_cluster set state='Destroyed', removed=now() where uuid = '%s';" % cluster.id) + else: + raise Exception("Error: Exception during delete cluster : %s" % e) + + if verify and not forceDeleted: + self.assertEqual( + delete_response.success, + True, + "Check KubernetesCluster delete response {}, {}".format(delete_response.success, True) + ) + + db_cluster_removed = \ + self.dbclient.execute("select removed from kubernetes_cluster where uuid = '%s';" % cluster_id)[0][0] + + self.assertNotEqual( + db_cluster_removed, + None, + "KubernetesCluster not removed in DB, {}".format(db_cluster_removed) + ) + + def create_aff_grp(self, aff_grp_name=None, aff_grp_type="host anti-affinity"): + """Create an affinity group""" + if aff_grp_name is None: + aff_grp_name = "aff_grp_" + random_gen(size=6) + + aff_grp_data = { + "name": aff_grp_name, + "type": aff_grp_type + } + aff_grp = AffinityGroup.create( + self.apiclient, + aff_grp_data, + self.account.name, + self.domain.id + ) + self.aff_grp.append(aff_grp) + self.cleanup.append(aff_grp) + return aff_grp + + def createKubernetesCluster(self, name, version_id, size=1, control_nodes=1, etcd_nodes=0, + control_aff_grp=None, worker_aff_grp=None, etcd_aff_grp=None): + """Create a Kubernetes cluster with optional affinity groups for each node type""" + createKubernetesClusterCmd = createKubernetesCluster.createKubernetesClusterCmd() + createKubernetesClusterCmd.name = name + createKubernetesClusterCmd.description = name + "-description" + createKubernetesClusterCmd.kubernetesversionid = version_id + createKubernetesClusterCmd.size = size + createKubernetesClusterCmd.controlnodes = control_nodes + createKubernetesClusterCmd.serviceofferingid = self.cks_service_offering.id + createKubernetesClusterCmd.zoneid = self.zone.id + createKubernetesClusterCmd.noderootdisksize = 10 + createKubernetesClusterCmd.account = self.account.name + createKubernetesClusterCmd.domainid = self.domain.id + + if etcd_nodes > 0: + createKubernetesClusterCmd.etcdnodes = etcd_nodes + + # Set affinity groups for node types using the nodeaffinitygroups parameter + # Format: list of {node: "", affinitygroup: ""} + if control_aff_grp is not None: + if not hasattr(createKubernetesClusterCmd, 'nodeaffinitygroups'): + createKubernetesClusterCmd.nodeaffinitygroups = [] + createKubernetesClusterCmd.nodeaffinitygroups.append({ + "node": "CONTROL", + "affinitygroup": control_aff_grp.id + }) + if worker_aff_grp is not None: + if not hasattr(createKubernetesClusterCmd, 'nodeaffinitygroups'): + createKubernetesClusterCmd.nodeaffinitygroups = [] + createKubernetesClusterCmd.nodeaffinitygroups.append({ + "node": "WORKER", + "affinitygroup": worker_aff_grp.id + }) + if etcd_aff_grp is not None: + if not hasattr(createKubernetesClusterCmd, 'nodeaffinitygroups'): + createKubernetesClusterCmd.nodeaffinitygroups = [] + createKubernetesClusterCmd.nodeaffinitygroups.append({ + "node": "ETCD", + "affinitygroup": etcd_aff_grp.id + }) + + if self.default_network: + createKubernetesClusterCmd.networkid = self.default_network.id + + clusterResponse = self.apiclient.createKubernetesCluster(createKubernetesClusterCmd) + return clusterResponse + + def startKubernetesCluster(self, cluster_id): + startKubernetesClusterCmd = startKubernetesCluster.startKubernetesClusterCmd() + startKubernetesClusterCmd.id = cluster_id + response = self.apiclient.startKubernetesCluster(startKubernetesClusterCmd) + return response + + def scaleKubernetesCluster(self, cluster_id, size): + scaleKubernetesClusterCmd = scaleKubernetesCluster.scaleKubernetesClusterCmd() + scaleKubernetesClusterCmd.id = cluster_id + scaleKubernetesClusterCmd.size = size + response = self.apiclient.scaleKubernetesCluster(scaleKubernetesClusterCmd) + return response + + def verifyKubernetesClusterState(self, cluster_response, state): + """Check if Kubernetes cluster state matches expected state""" + self.assertEqual( + cluster_response.state, + state, + "Check KubernetesCluster state {}, expected {}".format(cluster_response.state, state) + ) + + def verifyKubernetesClusterAffinityGroups(self, cluster, control_aff_grp=None, + worker_aff_grp=None, etcd_aff_grp=None): + """Verify affinity groups are correctly assigned to the cluster""" + if control_aff_grp is not None: + self.assertEqual( + cluster.controlnodeaffinitygroupid, + control_aff_grp.id, + "Control node affinity group ID mismatch. Expected: {}, Got: {}".format( + control_aff_grp.id, cluster.controlnodeaffinitygroupid) + ) + self.assertEqual( + cluster.controlnodeaffinitygroupname, + control_aff_grp.name, + "Control node affinity group name mismatch. Expected: {}, Got: {}".format( + control_aff_grp.name, cluster.controlnodeaffinitygroupname) + ) + else: + self.assertTrue( + not hasattr(cluster, 'controlnodeaffinitygroupid') or cluster.controlnodeaffinitygroupid is None, + "Control node affinity group should be None" + ) + + if worker_aff_grp is not None: + self.assertEqual( + cluster.workernodeaffinitygroupid, + worker_aff_grp.id, + "Worker node affinity group ID mismatch. Expected: {}, Got: {}".format( + worker_aff_grp.id, cluster.workernodeaffinitygroupid) + ) + self.assertEqual( + cluster.workernodeaffinitygroupname, + worker_aff_grp.name, + "Worker node affinity group name mismatch. Expected: {}, Got: {}".format( + worker_aff_grp.name, cluster.workernodeaffinitygroupname) + ) + else: + self.assertTrue( + not hasattr(cluster, 'workernodeaffinitygroupid') or cluster.workernodeaffinitygroupid is None, + "Worker node affinity group should be None" + ) + + if etcd_aff_grp is not None: + self.assertEqual( + cluster.etcdnodeaffinitygroupid, + etcd_aff_grp.id, + "ETCD node affinity group ID mismatch. Expected: {}, Got: {}".format( + etcd_aff_grp.id, cluster.etcdnodeaffinitygroupid) + ) + self.assertEqual( + cluster.etcdnodeaffinitygroupname, + etcd_aff_grp.name, + "ETCD node affinity group name mismatch. Expected: {}, Got: {}".format( + etcd_aff_grp.name, cluster.etcdnodeaffinitygroupname) + ) + else: + self.assertTrue( + not hasattr(cluster, 'etcdnodeaffinitygroupid') or cluster.etcdnodeaffinitygroupid is None, + "ETCD node affinity group should be None" + ) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_01_create_cluster_with_control_node_affinity_group(self): + """Test creating a Kubernetes cluster with affinity group for control nodes only + + # Validate the following: + # 1. Create an affinity group + # 2. Create a Kubernetes cluster with the affinity group for control nodes + # 3. Verify cluster is created successfully and affinity group is assigned + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity group for control nodes") + control_aff_grp = self.create_aff_grp(aff_grp_name="control-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with control node affinity group") + cluster_name = "cks-aff-grp-control-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + control_aff_grp=control_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=control_aff_grp, + worker_aff_grp=None, + etcd_aff_grp=None + ) + + self.debug("Kubernetes cluster with control node affinity group created successfully") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_02_create_cluster_with_worker_node_affinity_group(self): + """Test creating a Kubernetes cluster with affinity group for worker nodes only + + # Validate the following: + # 1. Create an affinity group + # 2. Create a Kubernetes cluster with the affinity group for worker nodes + # 3. Verify cluster is created successfully and affinity group is assigned + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity group for worker nodes") + worker_aff_grp = self.create_aff_grp(aff_grp_name="worker-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with worker node affinity group") + cluster_name = "cks-aff-grp-worker-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + worker_aff_grp=worker_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=None, + worker_aff_grp=worker_aff_grp, + etcd_aff_grp=None + ) + + self.debug("Kubernetes cluster with worker node affinity group created successfully") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_03_create_cluster_with_all_node_type_affinity_groups(self): + """Test creating a Kubernetes cluster with different affinity groups for all node types + + # Validate the following: + # 1. Create separate affinity groups for control, worker, and etcd nodes + # 2. Create a Kubernetes cluster with affinity groups for all node types + # 3. Verify cluster is created successfully and all affinity groups are assigned + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity groups for all node types") + control_aff_grp = self.create_aff_grp(aff_grp_name="control-aff-grp-" + random_gen()) + worker_aff_grp = self.create_aff_grp(aff_grp_name="worker-aff-grp-" + random_gen()) + etcd_aff_grp = self.create_aff_grp(aff_grp_name="etcd-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with affinity groups for all node types") + cluster_name = "cks-aff-grp-all-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + etcd_nodes=1, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp, + etcd_aff_grp=etcd_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp, + etcd_aff_grp=etcd_aff_grp + ) + + self.debug("Kubernetes cluster with all node type affinity groups created successfully") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_04_create_cluster_with_same_affinity_group_all_node_types(self): + """Test creating a Kubernetes cluster with the same affinity group for all node types + + # Validate the following: + # 1. Create a single affinity group + # 2. Create a Kubernetes cluster using the same affinity group for all node types + # 3. Verify cluster is created successfully + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating a single affinity group for all node types") + shared_aff_grp = self.create_aff_grp(aff_grp_name="shared-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with same affinity group for all node types") + cluster_name = "cks-aff-grp-shared-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + control_aff_grp=shared_aff_grp, + worker_aff_grp=shared_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=shared_aff_grp, + worker_aff_grp=shared_aff_grp, + etcd_aff_grp=None + ) + + self.debug("Kubernetes cluster with shared affinity group created successfully") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_05_scale_cluster_respects_worker_affinity_group(self): + """Test that scaling a cluster respects the worker node affinity group + + # Validate the following: + # 1. Create a cluster with worker node affinity group + # 2. Scale up the cluster + # 3. Verify affinity group assignments are preserved after scaling + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity group for worker nodes") + worker_aff_grp = self.create_aff_grp(aff_grp_name="worker-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with worker node affinity group") + cluster_name = "cks-aff-grp-scale-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + worker_aff_grp=worker_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + + self.debug("Scaling up Kubernetes cluster from 1 to 2 worker nodes") + cluster = self.scaleKubernetesCluster(cluster.id, 2) + + self.verifyKubernetesClusterState(cluster, 'Running') + self.assertEqual( + cluster.size, + 2, + "Cluster size should be 2 after scaling up, got {}".format(cluster.size) + ) + + # Verify affinity group is still assigned after scaling + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=None, + worker_aff_grp=worker_aff_grp, + etcd_aff_grp=None + ) + + self.debug("Kubernetes cluster scaled successfully with affinity group preserved") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_06_stop_start_cluster_preserves_affinity_groups(self): + """Test that stopping and starting a cluster preserves affinity groups + + # Validate the following: + # 1. Create a cluster with affinity groups + # 2. Stop the cluster + # 3. Start the cluster + # 4. Verify affinity groups are preserved + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity groups") + control_aff_grp = self.create_aff_grp(aff_grp_name="control-aff-grp-" + random_gen()) + worker_aff_grp = self.create_aff_grp(aff_grp_name="worker-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with affinity groups") + cluster_name = "cks-aff-grp-lifecycle-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + + self.debug("Stopping Kubernetes cluster") + self.stopKubernetesCluster(cluster.id) + + cluster = self.listKubernetesCluster(cluster.id) + self.assertEqual( + cluster.state, + 'Stopped', + "Cluster should be in Stopped state, got {}".format(cluster.state) + ) + + self.debug("Starting Kubernetes cluster") + cluster = self.startKubernetesCluster(cluster.id) + + self.verifyKubernetesClusterState(cluster, 'Running') + + # Verify affinity groups are preserved after stop/start + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp, + etcd_aff_grp=None + ) + + self.debug("Kubernetes cluster stop/start completed with affinity groups preserved") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_07_create_cluster_with_invalid_affinity_group_id(self): + """Test creating a cluster with an invalid affinity group ID fails + + # Validate the following: + # 1. Attempt to create a cluster with a non-existent affinity group ID + # 2. Verify the operation fails with appropriate error + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating Kubernetes cluster with invalid affinity group ID") + cluster_name = "cks-aff-grp-invalid-" + random_gen() + + # Create a fake affinity group object with invalid ID + class FakeAffinityGroup: + def __init__(self): + self.id = "invalid-uuid-12345" + self.name = "fake-group" + + fake_aff_grp = FakeAffinityGroup() + + with self.assertRaises(Exception) as context: + self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + control_aff_grp=fake_aff_grp + ) + + self.debug("Expected error when creating cluster with invalid affinity group: %s" % context.exception) + + # Clean up any partially created cluster + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_08_cluster_response_includes_affinity_group_details(self): + """Test that cluster list response includes affinity group details + + # Validate the following: + # 1. Create a cluster with affinity groups + # 2. List the cluster + # 3. Verify response includes affinity group IDs and names + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity groups") + control_aff_grp = self.create_aff_grp(aff_grp_name="control-aff-grp-" + random_gen()) + worker_aff_grp = self.create_aff_grp(aff_grp_name="worker-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with affinity groups") + cluster_name = "cks-aff-grp-response-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + + # List the cluster and verify response + listed_cluster = self.listKubernetesCluster(cluster.id) + + self.assertIsNotNone(listed_cluster, "Cluster should be listed") + + # Verify control node affinity group in response + self.assertTrue( + hasattr(listed_cluster, 'controlnodeaffinitygroupid'), + "Response should include controlnodeaffinitygroupid" + ) + self.assertTrue( + hasattr(listed_cluster, 'controlnodeaffinitygroupname'), + "Response should include controlnodeaffinitygroupname" + ) + + # Verify worker node affinity group in response + self.assertTrue( + hasattr(listed_cluster, 'workernodeaffinitygroupid'), + "Response should include workernodeaffinitygroupid" + ) + self.assertTrue( + hasattr(listed_cluster, 'workernodeaffinitygroupname'), + "Response should include workernodeaffinitygroupname" + ) + + # Verify the values match + self.verifyKubernetesClusterAffinityGroups( + listed_cluster, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp, + etcd_aff_grp=None + ) + + self.debug("Cluster response includes correct affinity group details") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_09_create_cluster_without_affinity_groups(self): + """Test creating a cluster without any affinity groups + + # Validate the following: + # 1. Create a cluster without specifying any affinity groups + # 2. Verify cluster is created and affinity group fields are null/empty + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating Kubernetes cluster without affinity groups") + cluster_name = "cks-no-aff-grp-" + random_gen() + try: + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1 + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + + # Verify no affinity groups are assigned + self.verifyKubernetesClusterAffinityGroups( + cluster, + control_aff_grp=None, + worker_aff_grp=None, + etcd_aff_grp=None + ) + + self.debug("Kubernetes cluster created successfully without affinity groups") + finally: + cluster = self.listKubernetesCluster(cluster_name=cluster_name) + if cluster is not None: + self.deleteKubernetesClusterAndVerify(cluster.id, False, True) + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_10_delete_cluster_with_affinity_groups(self): + """Test that deleting a cluster with affinity groups works correctly + + # Validate the following: + # 1. Create a cluster with affinity groups + # 2. Delete the cluster + # 3. Verify cluster is deleted and affinity groups still exist + """ + if self.setup_failed: + self.fail("Setup incomplete") + + self.debug("Creating affinity groups") + control_aff_grp = self.create_aff_grp(aff_grp_name="control-aff-grp-" + random_gen()) + worker_aff_grp = self.create_aff_grp(aff_grp_name="worker-aff-grp-" + random_gen()) + + self.debug("Creating Kubernetes cluster with affinity groups") + cluster_name = "cks-aff-grp-delete-" + random_gen() + cluster = self.createKubernetesCluster( + cluster_name, + self.kubernetes_version.id, + size=1, + control_nodes=1, + control_aff_grp=control_aff_grp, + worker_aff_grp=worker_aff_grp + ) + + self.verifyKubernetesClusterState(cluster, 'Running') + cluster_id = cluster.id + + self.debug("Deleting Kubernetes cluster") + self.deleteKubernetesClusterAndVerify(cluster_id) + + # Verify cluster is deleted + deleted_cluster = self.listKubernetesCluster(cluster_id) + self.assertIsNone(deleted_cluster, "Cluster should be deleted") + + # Verify affinity groups still exist + control_aff_grp_list = AffinityGroup.list(self.apiclient, id=control_aff_grp.id) + self.assertIsNotNone(control_aff_grp_list, "Control affinity group should still exist") + + worker_aff_grp_list = AffinityGroup.list(self.apiclient, id=worker_aff_grp.id) + self.assertIsNotNone(worker_aff_grp_list, "Worker affinity group should still exist") + + self.debug("Kubernetes cluster deleted successfully, affinity groups preserved") diff --git a/test/integration/component/test_multiple_subnets_in_isolated_network.py b/test/integration/component/test_multiple_subnets_in_isolated_network.py index 278728b2adbb..f5efd9ac893d 100644 --- a/test/integration/component/test_multiple_subnets_in_isolated_network.py +++ b/test/integration/component/test_multiple_subnets_in_isolated_network.py @@ -210,9 +210,9 @@ def verify_router_publicnic_state(self, router, host, publicNics): self.logger.debug("Skip as redundant_state is %s" % redundant_state) return elif redundant_state == "PRIMARY": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state DOWN" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state DOWN" |wc -l' % publicNics elif redundant_state == "BACKUP": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state UP" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state UP" |wc -l' % publicNics result = get_process_status( host.ipaddress, host.port, diff --git a/test/integration/component/test_multiple_subnets_in_isolated_network_rvr.py b/test/integration/component/test_multiple_subnets_in_isolated_network_rvr.py index d83571f9210e..b6c6434f29b5 100644 --- a/test/integration/component/test_multiple_subnets_in_isolated_network_rvr.py +++ b/test/integration/component/test_multiple_subnets_in_isolated_network_rvr.py @@ -210,9 +210,9 @@ def verify_router_publicnic_state(self, router, host, publicNics): self.logger.debug("Skip as redundant_state is %s" % redundant_state) return elif redundant_state == "PRIMARY": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state DOWN" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state DOWN" |wc -l' % publicNics elif redundant_state == "BACKUP": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state UP" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state UP" |wc -l' % publicNics result = get_process_status( host.ipaddress, host.port, diff --git a/test/integration/component/test_multiple_subnets_in_vpc.py b/test/integration/component/test_multiple_subnets_in_vpc.py index 9167f156e19f..020665969959 100644 --- a/test/integration/component/test_multiple_subnets_in_vpc.py +++ b/test/integration/component/test_multiple_subnets_in_vpc.py @@ -214,9 +214,9 @@ def verify_router_publicnic_state(self, router, host, publicNics): self.logger.debug("Skip as redundant_state is %s" % redundant_state) return elif redundant_state == "PRIMARY": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state DOWN" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state DOWN" |wc -l' % publicNics elif redundant_state == "BACKUP": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state UP" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state UP" |wc -l' % publicNics result = get_process_status( host.ipaddress, host.port, diff --git a/test/integration/component/test_multiple_subnets_in_vpc_rvr.py b/test/integration/component/test_multiple_subnets_in_vpc_rvr.py index 7c7127307b5c..e8963f187554 100644 --- a/test/integration/component/test_multiple_subnets_in_vpc_rvr.py +++ b/test/integration/component/test_multiple_subnets_in_vpc_rvr.py @@ -214,9 +214,9 @@ def verify_router_publicnic_state(self, router, host, publicNics): self.logger.debug("Skip as redundant_state is %s" % redundant_state) return elif redundant_state == "PRIMARY": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state DOWN" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state DOWN" |wc -l' % publicNics elif redundant_state == "BACKUP": - command = 'ip link show |grep BROADCAST | egrep "%s" |grep "state UP" |wc -l' % publicNics + command = 'ip link show |grep BROADCAST | grep -E "%s" |grep "state UP" |wc -l' % publicNics result = get_process_status( host.ipaddress, host.port, diff --git a/test/integration/component/test_netscaler_nw_off.py b/test/integration/component/test_netscaler_nw_off.py index d9f6c9724f63..627c4a01c052 100644 --- a/test/integration/component/test_netscaler_nw_off.py +++ b/test/integration/component/test_netscaler_nw_off.py @@ -802,7 +802,7 @@ def test_02_multiple_netscaler_capacilty(self): # Validate the following # 1. Add another netscaler device and spawn a new VM again - # 2. VM deployement should be successful + # 2. VM deployment should be successful self.debug("Adding another netscaler device: %s" % self.services["netscaler_2"]["ipaddress"]) @@ -1672,7 +1672,7 @@ def test_shared_to_dedicated_upgrade(self): # LB rules # 5. Deploy instance with dedicated network offering in account 3. # Create Lb rules. - # 6. Configure another instace of netscaler in dedicated mode + # 6. Configure another instance of netscaler in dedicated mode # 7. upgrade networkj for user 1 to dedicated network offering. # Create LB rules. LB rule creation should be successful @@ -2063,7 +2063,7 @@ def test_shared_to_dedicated_upgrade(self): # LB rules # 5. Deploy instance with dedicated network offering in account 3. # Create Lb rules. - # 6. Configure another instace of netscaler in dedicated mode + # 6. Configure another instance of netscaler in dedicated mode # 7. upgrade networkj for user 1 to dedicated network offering. # Create LB rules. LB rule creation should be successful diff --git a/test/integration/component/test_project_resources.py b/test/integration/component/test_project_resources.py index 16d21dfd7cba..37973eb3422a 100644 --- a/test/integration/component/test_project_resources.py +++ b/test/integration/component/test_project_resources.py @@ -1283,18 +1283,18 @@ def test_08_security_group(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, projectid=self.project.id ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertNotEqual( - len(sercurity_groups), + len(security_groups), 0, "Check List Security groups response" ) diff --git a/test/integration/component/test_project_usage.py b/test/integration/component/test_project_usage.py index ea7f740be856..bfb0f0b6d08a 100644 --- a/test/integration/component/test_project_usage.py +++ b/test/integration/component/test_project_usage.py @@ -1707,17 +1707,10 @@ def setUpClass(cls): listall=True ) if isinstance(networks, list): - network = networks[0] + cls.network = networks[0] else: raise Exception("List networks call failed") - cls.public_ip = PublicIPAddress.create( - cls.api_client, - zoneid=cls.zone.zoneid, - services=cls.services["server"], - networkid=network.id, - projectid=cls.project.id - ) cls._cleanup = [ cls.project, cls.service_offering, @@ -1760,12 +1753,20 @@ def test_01_vpn_usage(self): # this account in cloud.usage_event table # 4. Delete this account. - self.debug("Created VPN with public IP: %s" % - self.public_ip.ipaddress.id) - #Assign VPN to Public IP + # Listing source NAT IP of newly added network + ipAddresses = PublicIPAddress.list( + self.apiclient, + associatednetworkid=self.network.id, + projectid=self.project.id, + issourcenat=True) + + sourceNatIP = ipAddresses[0] + self.debug("Created VPN with source NAT IP: %s" % + sourceNatIP.id) + #Assign VPN to source NAT IP vpn = Vpn.create( self.apiclient, - self.public_ip.ipaddress.id, + sourceNatIP.id, projectid=self.project.id ) diff --git a/test/integration/component/test_rootvolume_resize.py b/test/integration/component/test_rootvolume_resize.py index 11e95e892964..819ac3ce47f8 100644 --- a/test/integration/component/test_rootvolume_resize.py +++ b/test/integration/component/test_rootvolume_resize.py @@ -308,7 +308,7 @@ def getDiskController(self, vm, diskcontroller="ide"): vmid = int(qresultvmuuid[0][0]) qresult = self.dbclient.execute( "select rootDiskController from" - " user_vm_details where id = '%s';" % vmid + " vm_instance_details where id = '%s';" % vmid ) self.debug("Query result: %s" % qresult) diskcontroller = qresult[0][0] diff --git a/test/integration/component/test_security_groups.py b/test/integration/component/test_security_groups.py index 449fe10cb284..04d4f0713fb9 100644 --- a/test/integration/component/test_security_groups.py +++ b/test/integration/component/test_security_groups.py @@ -227,25 +227,25 @@ def test_02_listSecurityGroups(self): # for the admin account # 3. No Ingress Rules should be part of the default security group - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertNotEqual( - len(sercurity_groups), + len(security_groups), 0, "Check List Security groups response" ) self.debug("List Security groups response: %s" % - str(sercurity_groups)) + str(security_groups)) self.assertEqual( - hasattr(sercurity_groups, 'ingressrule'), + hasattr(security_groups, 'ingressrule'), False, "Check ingress rule attribute for default security group" ) @@ -306,26 +306,26 @@ def test_03_accessInDefaultSecurityGroup(self): "Check virtual machine displayname in listVirtualMachines" ) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.debug("List Security groups response: %s" % - str(sercurity_groups)) + str(security_groups)) self.assertNotEqual( - len(sercurity_groups), + len(security_groups), 0, "Check List Security groups response" ) self.assertEqual( - hasattr(sercurity_groups, 'ingressrule'), + hasattr(security_groups, 'ingressrule'), False, "Check ingress rule attribute for default security group" ) @@ -432,19 +432,19 @@ def test_01_authorizeIngressRule(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -580,19 +580,19 @@ def test_01_revokeIngressRule(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -909,18 +909,18 @@ def test_01_deployVMWithUserData(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1072,19 +1072,19 @@ def test_01_delete_security_grp_running_vm(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1126,12 +1126,12 @@ def test_01_delete_security_grp_running_vm(self): time.sleep(self.testdata["sleep"]) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, id=security_group.id ) self.assertNotEqual( - sercurity_groups, + security_groups, None, "Check List Security groups response" ) @@ -1156,18 +1156,18 @@ def test_02_delete_security_grp_withoout_running_vm(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1303,18 +1303,18 @@ def test_01_authorizeIngressRule_AfterDeployVM(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1425,18 +1425,18 @@ def test_02_revokeIngressRule_AfterDeployVM(self): self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) @@ -1594,19 +1594,19 @@ def test_03_stopStartVM_verifyIngressAccess(self): ) self.debug("Created security group with ID: %s" % security_group.id) # Default Security group should not have any ingress rule - sercurity_groups = SecurityGroup.list( + security_groups = SecurityGroup.list( self.apiclient, account=self.account.name, domainid=self.account.domainid ) self.assertEqual( - isinstance(sercurity_groups, list), + isinstance(security_groups, list), True, "Check for list security groups response" ) self.assertEqual( - len(sercurity_groups), + len(security_groups), 2, "Check List Security groups response" ) diff --git a/test/integration/component/test_stopped_vm.py b/test/integration/component/test_stopped_vm.py index b7fe4b04b043..4e6fa1e37740 100644 --- a/test/integration/component/test_stopped_vm.py +++ b/test/integration/component/test_stopped_vm.py @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. -""" P1 for stopped Virtual Maschine life cycle +""" P1 for stopped Virtual Machine life cycle """ # Import Local Modules from nose.plugins.attrib import attr diff --git a/test/integration/component/test_update_vm.py b/test/integration/component/test_update_vm.py index 4d167c633300..9629faf0cacf 100644 --- a/test/integration/component/test_update_vm.py +++ b/test/integration/component/test_update_vm.py @@ -186,7 +186,7 @@ def test_update_vm_details_admin(self): vm_db_id = dbresult[0][0] self.debug("VM has database id %d" % vm_db_id) - self.dbclient.execute("insert into user_vm_details (vm_id, name, value, display) values (%d,'%s','HOST', 0)" % (vm_db_id, hidden_detail_name)) + self.dbclient.execute("insert into vm_instance_details (vm_id, name, value, display) values (%d,'%s','HOST', 0)" % (vm_db_id, hidden_detail_name)) vm = self.listVmById(self.virtual_machine.id) self.debug("VirtualMachine fetched with details: %s of type %s" % (vm.details, type(vm.details))) @@ -227,7 +227,7 @@ def test_update_vm_details_admin(self): def detailInDatabase(self, vm_id, detail_name): - dbresult = self.dbclient.execute("select id from user_vm_details where vm_id=%s and name='%s'" % (vm_id, detail_name)) + dbresult = self.dbclient.execute("select id from vm_instance_details where vm_id=%s and name='%s'" % (vm_id, detail_name)) self.debug("Detail %s for VM %s: %s" % (detail_name, vm_id, dbresult)) if validateList(dbresult)[0] == PASS: return True diff --git a/test/integration/component/test_vpc_network_lbrules.py b/test/integration/component/test_vpc_network_lbrules.py index 8f137adf78d5..395f5876099e 100644 --- a/test/integration/component/test_vpc_network_lbrules.py +++ b/test/integration/component/test_vpc_network_lbrules.py @@ -939,10 +939,6 @@ def test_15_VPC_ReleaseIPForLBRuleCreated(self): lb_rule = self.create_LB_Rule(public_ip_1, network_1, [vm_2, vm_1]) public_ip_1.delete(self.apiclient) self.cleanup.remove(public_ip_1) - - with self.assertRaises(Exception): - lb_rules = LoadBalancerRule.list(self.apiclient, - id=lb_rule.id, - listall=True - ) + lb_rule_after_deletion = LoadBalancerRule.list(self.apiclient, id=lb_rule.id, listall=True) + self.assertEqual(lb_rule_after_deletion, None, 'Load balancer rule should be deleted.') return diff --git a/test/integration/component/test_vpc_offerings.py b/test/integration/component/test_vpc_offerings.py index 25206cfe1569..a47bc119a88c 100644 --- a/test/integration/component/test_vpc_offerings.py +++ b/test/integration/component/test_vpc_offerings.py @@ -868,10 +868,10 @@ def test_06_vpc_off_invalid_services(self): # Validate the following # 1. Creating VPC Offering with services NOT supported by VPC - # like Firewall should not be allowed + # like SecurityGroup should not be allowed - self.logger.debug("Creating a VPC offering with Firewall") - self.services["vpc_offering"]["supportedservices"] = 'Dhcp,Dns,PortForwarding,Firewall,Vpn,SourceNat,Lb,UserData,StaticNat' + self.logger.debug("Creating a VPC offering with SecurityGroup") + self.services["vpc_offering"]["supportedservices"] = 'Dhcp,Dns,PortForwarding,SecurityGroup,Vpn,SourceNat,Lb,UserData,StaticNat' with self.assertRaises(Exception): VpcOffering.create( diff --git a/test/integration/component/test_vpc_vm_life_cycle.py b/test/integration/component/test_vpc_vm_life_cycle.py index abe6d194a54f..d487a7fa1bdb 100644 --- a/test/integration/component/test_vpc_vm_life_cycle.py +++ b/test/integration/component/test_vpc_vm_life_cycle.py @@ -1274,7 +1274,7 @@ def test_02_deploy_vm_vpcvr_stopped(self): except Exception as e: self.fail("Failed to deploy the virtual instance: %s" % e) - self.debug("Verify the deployment of virtual instace") + self.debug("Verify the deployment of virtual instance") vms = VirtualMachine.list( self.apiclient, id=vm.id, diff --git a/test/integration/component/test_vpn_users.py b/test/integration/component/test_vpn_users.py index 980e21bba271..ee7a0ea3ee0a 100644 --- a/test/integration/component/test_vpn_users.py +++ b/test/integration/component/test_vpn_users.py @@ -165,18 +165,18 @@ def tearDown(self): def create_VPN(self, public_ip): """Creates VPN for the network""" - self.debug("Creating VPN with public IP: %s" % public_ip.ipaddress.id) + self.debug("Creating VPN with public IP: %s" % public_ip.id) try: # Assign VPN to Public IP vpn = Vpn.create(self.apiclient, - self.public_ip.ipaddress.id, + public_ip.id, account=self.account.name, domainid=self.account.domainid) self.cleanup.append(vpn) self.debug("Verifying the remote VPN access") vpns = Vpn.list(self.apiclient, - publicipid=public_ip.ipaddress.id, + publicipid=public_ip.id, listall=True) self.assertEqual( isinstance(vpns, list), @@ -244,7 +244,23 @@ def test_01_VPN_user_limit(self): self.debug("Enabling the VPN access for IP: %s" % self.public_ip.ipaddress) - self.create_VPN(self.public_ip) + network_id = self.virtual_machine.nic[0].networkid + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + associatednetworkid=network_id + ) + self.assertEqual( + validateList(src_nat_list)[0], + PASS, + "Failed to list source nat ip address" + ) + ip = src_nat_list[0] + + self.create_VPN(ip) self.debug("Creating %s VPN users" % limit) for x in range(limit): self.create_VPN_Users() @@ -296,9 +312,24 @@ def test_03_enable_vpn_use_port(self): # 3. add a port forward rule for UDP port 1701. Should result in error # saying that VPN is enabled over port 1701 + network_id = self.virtual_machine.nic[0].networkid + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + associatednetworkid=network_id + ) + self.assertEqual( + validateList(src_nat_list)[0], + PASS, + "Failed to list source nat ip address" + ) + ip = src_nat_list[0] self.debug("Enabling the VPN connection for IP: %s" % - self.public_ip.ipaddress) - self.create_VPN(self.public_ip) + ip.address) + self.create_VPN(ip) self.debug("Creating a port forwarding rule on port 1701") # Create NAT rule @@ -307,7 +338,7 @@ def test_03_enable_vpn_use_port(self): self.apiclient, self.virtual_machine, self.services["natrule"], - self.public_ip.ipaddress.id) + ip.id) self.debug("Create NAT rule failed! Test successful!") return @@ -322,9 +353,25 @@ def test_04_add_new_users(self): # 3. We should be able to successfully establish a VPN connection using # the newly added user credential. + network_id = self.virtual_machine.nic[0].networkid + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + associatednetworkid=network_id + ) + self.assertEqual( + validateList(src_nat_list)[0], + PASS, + "Failed to list source nat ip address" + ) + ip = src_nat_list[0] + self.debug("Enabling the VPN connection for IP: %s" % - self.public_ip.ipaddress) - self.create_VPN(self.public_ip) + ip.address) + self.create_VPN(ip) try: self.debug("Adding new VPN user to account: %s" % @@ -349,9 +396,24 @@ def test_05_add_duplicate_user(self): # 2. Add a VPN user say "abc" that already an added user to the VPN. # 3. Adding this VPN user should fail. + network_id = self.virtual_machine.nic[0].networkid + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + associatednetworkid=network_id + ) + self.assertEqual( + validateList(src_nat_list)[0], + PASS, + "Failed to list source nat ip address" + ) + ip = src_nat_list[0] self.debug("Enabling the VPN connection for IP: %s" % - self.public_ip.ipaddress) - self.create_VPN(self.public_ip) + ip.address) + self.create_VPN(ip) self.debug("Adding new VPN user to account: %s" % self.account.name) @@ -379,9 +441,24 @@ def test_06_add_VPN_user_global_admin(self): # 2. We should be able to use this newly created user credential to # establish VPN connection that will give access all VMs of this user + network_id = self.virtual_machine.nic[0].networkid + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + associatednetworkid=network_id + ) + self.assertEqual( + validateList(src_nat_list)[0], + PASS, + "Failed to list source nat ip address" + ) + ip = src_nat_list[0] self.debug("Enabling VPN connection to account: %s" % self.account.name) - self.create_VPN(self.public_ip) + self.create_VPN(ip) self.debug("Creating VPN user for the account: %s" % self.account.name) self.create_VPN_Users() @@ -422,9 +499,24 @@ def test_07_add_VPN_user_domain_admin(self): # 2. We should be able to use this newly created user credential to # establish VPN connection that will give access all VMs of this user + network_id = self.virtual_machine.nic[0].networkid + src_nat_list = PublicIPAddress.list( + self.apiclient, + account=self.account.name, + domainid=self.account.domainid, + listall=True, + issourcenat=True, + associatednetworkid=network_id + ) + self.assertEqual( + validateList(src_nat_list)[0], + PASS, + "Failed to list source nat ip address" + ) + ip = src_nat_list[0] self.debug("Enabling VPN connection to account: %s" % self.account.name) - self.create_VPN(self.public_ip) + self.create_VPN(ip) self.debug("Creating VPN user for the account: %s" % self.account.name) self.create_VPN_Users() diff --git a/test/integration/plugins/linstor/README.md b/test/integration/plugins/linstor/README.md index 717f4e24e70f..4971c9506b5b 100644 --- a/test/integration/plugins/linstor/README.md +++ b/test/integration/plugins/linstor/README.md @@ -1,9 +1,28 @@ + + # Linstor storage plugin -================================== + This directory contains the basic VM, Volume life cycle tests for Linstor storage pool (in KVM hypervisor). -# Running tests -=============== +## Running tests + To run the basic volume tests, first update the below test data of the CloudStack environment ``` @@ -29,3 +48,21 @@ nosetests --with-marvin --marvin-config= /test/ ``` You can also run these tests out of the box with PyDev or PyCharm or whatever. + +## Encrypted snapshot tests + +`test_linstor_encrypted_snapshots.py` covers the encrypted-volume snapshot round trip +(create encrypted root disk -> snapshot -> revert / create-volume-from-snapshot) and that the +backed-up qcow2 on secondary storage is itself LUKS encrypted. + +Extra prerequisites: + +* At least one KVM host with volume-encryption support (`host.encryptionsupported == true`, i.e. + cryptsetup/qemu LUKS available). Tests self-skip if none is found. +* The Linstor resource group used (`acs-basic`) must be able to add a LUKS layer to its volumes. +* `lin.backup.snapshots` must be enabled (default) so snapshots are backed up to secondary storage; + the test sets it. With it disabled the qcow2 path is not exercised. + +``` +nosetests --with-marvin --marvin-config= /test/integration/plugins/linstor/test_linstor_encrypted_snapshots.py --zone= --hypervisor=kvm +``` diff --git a/test/integration/plugins/linstor/test_linstor_encrypted_snapshots.py b/test/integration/plugins/linstor/test_linstor_encrypted_snapshots.py new file mode 100644 index 000000000000..5f440309bb34 --- /dev/null +++ b/test/integration/plugins/linstor/test_linstor_encrypted_snapshots.py @@ -0,0 +1,444 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import json +import logging +import os +import random +import socket +import time + +# All tests inherit from cloudstackTestCase +from marvin.cloudstackTestCase import cloudstackTestCase + +# Import Integration Libraries +from marvin.cloudstackAPI import createVolume +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.base import Account, Configurations, Host, ServiceOffering, \ + Snapshot, StoragePool, User, VirtualMachine, Volume +from marvin.lib.common import get_domain, get_template, get_zone, list_hosts, list_virtual_machines, list_volumes +from marvin.lib.utils import cleanup_resources +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr + +# Prerequisites: +# Only one zone / pod / cluster +# Only KVM hypervisor (Linstor only supports KVM) +# At least one KVM host with volume-encryption support (host.encryptionsupported == True), +# i.e. cryptsetup/qemu with LUKS available on the host. +# One Linstor storage pool whose resource-group can add a LUKS layer (encrypted volumes). +# 'lin.backup.snapshots' enabled (default true) so snapshots are backed up to secondary storage +# as qcow2 -- that is the path these tests are meant to exercise. With it disabled, snapshots +# stay on primary as Linstor system snapshots and a different (rollback) code path is used. +# +# What this exercises (the encrypted-snapshot round trip): +# * backup: decrypted DRBD device -> LUKS-encrypted qcow2 on secondary +# * revert: encrypted qcow2 -> decrypted, written to the DRBD device (Linstor re-encrypts) +# * create: encrypted qcow2 -> new volume via createVolumeFromSnapshot (KVMStorageProcessor) +# +# Note on verification: Linstor encrypts inside the DRBD stack (LUKS layer), so the libvirt domain +# XML does NOT carry like hypervisor-based encryption does. Correctness +# is therefore verified by a data round trip (write marker -> snapshot -> change -> restore -> read), +# and encryption-at-rest is verified by inspecting the backed-up qcow2 with 'qemu-img info'. + +MARKER_PATH = "/root/cs_enc_marker.txt" + + +class TestData: + account = "account" + computeOffering = "computeoffering" + diskName = "diskname" + domainId = "domainId" + hypervisor = "hypervisor" + provider = "provider" + scope = "scope" + storageTag = "linstor" + tags = "tags" + user = "user" + virtualMachine = "virtualmachine" + zoneId = "zoneId" + + def __init__(self, linstor_controller_url): + self.testdata = { + TestData.account: { + "email": "test-enc@test.com", + "firstname": "John", + "lastname": "Doe", + "username": "test-enc", + "password": "test" + }, + TestData.user: { + "email": "user-enc@test.com", + "firstname": "Jane", + "lastname": "Doe", + "username": "test-enc-user", + "password": "password" + }, + "primarystorage": { + "name": "LinstorEncPool-%d" % random.randint(0, 100000), + TestData.scope: "ZONE", + "url": linstor_controller_url, + TestData.provider: "Linstor", + TestData.tags: TestData.storageTag, + TestData.hypervisor: "KVM", + "details": { + "resourceGroup": "acs-basic" + } + }, + TestData.virtualMachine: { + "name": "TestEncVM", + "displayname": "Test Encrypted VM" + }, + # encryptroot=True is passed as a create kwarg, not in this dict + TestData.computeOffering: { + "name": "Linstor_Compute_Encrypted", + "displaytext": "Linstor_Compute_Encrypted", + "cpunumber": 1, + "cpuspeed": 500, + "memory": 512, + "storagetype": "shared", + TestData.tags: TestData.storageTag + }, + TestData.diskName: "restored-from-enc-snap", + TestData.zoneId: 1, + TestData.domainId: 1, + } + + +class ServiceReady: + @classmethod + def ready(cls, hostname, port): + try: + s = socket.create_connection((hostname, port), timeout=1) + s.close() + return True + except (ConnectionRefusedError, socket.timeout, OSError): + return False + + @classmethod + def wait(cls, hostname, port, wait_interval=5, timeout=120, service_name='ssh'): + starttime = int(round(time.time() * 1000)) + while not cls.ready(hostname, port): + if starttime + timeout * 1000 < int(round(time.time() * 1000)): + raise RuntimeError("{s} {h} cannot be reached.".format(s=service_name, h=hostname)) + time.sleep(wait_interval) + return True + + @classmethod + def wait_ssh_ready(cls, hostname, wait_interval=2, timeout=120): + return cls.wait(hostname, 22, wait_interval, timeout, "ssh") + + +class TestLinstorEncryptedSnapshots(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testclient = super(TestLinstorEncryptedSnapshots, cls).getClsTestClient() + + cls.apiClient = testclient.getApiClient() + cls.dbConnection = testclient.getDbConnection() + + cls._cleanup = [] + cls.skip_reason = None + + # Linstor is KVM-only, so the hypervisor type is not probed via getHypervisorInfo() (which is + # only populated when nosetests is invoked with --hypervisor). Instead we require an actual KVM + # host that supports volume encryption below. + + # The first host runs the Linstor controller (per the Linstor test prerequisites). + first_host = list_hosts(cls.apiClient)[0] + cls.testdata = TestData(first_host.ipaddress).testdata + + cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId]) + cls.domain = get_domain(cls.apiClient, cls.testdata[TestData.domainId]) + cls.template = get_template(cls.apiClient, cls.zone.id, hypervisor="KVM") + + # Host SSH credentials, only needed by test_03 to inspect the backed-up qcow2 on secondary + # storage. A full marvin config carries these under zones->pods->clusters->hosts, but a + # lightweight config may omit them; in that case fall back to HOST_SSH_USER / HOST_SSH_PASSWORD + # env vars. Never fail class setup over this - the other tests don't need host SSH. + cls.hostConfig = None + try: + cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0] \ + .__dict__["hosts"][0].__dict__ + except (KeyError, IndexError, AttributeError, TypeError): + host_user = os.environ.get("HOST_SSH_USER") + host_pass = os.environ.get("HOST_SSH_PASSWORD") + if host_user and host_pass: + cls.hostConfig = {"username": host_user, "password": host_pass} + + if not cls._encryption_capable_host_exists(): + cls.skip_reason = "No KVM host with volume-encryption support found" + return + + # Ensure snapshots are backed up to secondary storage (the path under test). + Configurations.update(cls.apiClient, name="lin.backup.snapshots", value="true") + + primarystorage = cls.testdata["primarystorage"] + # Registering the pool makes the management server call the Linstor controller (to read the + # resource-group capacity). If the controller enforces authentication, that call needs an API + # token, supplied as the 'lin.auth.apitoken' add-pool detail. Provide it via LINSTOR_API_TOKEN + # so it is never hard-coded; leave it unset for an unauthenticated controller. + api_token = os.environ.get("LINSTOR_API_TOKEN") + if api_token: + primarystorage["details"]["lin.auth.apitoken"] = api_token + + try: + cls.primary_storage = StoragePool.create( + cls.apiClient, + primarystorage, + scope=primarystorage[TestData.scope], + zoneid=cls.zone.id, + provider=primarystorage[TestData.provider], + tags=primarystorage[TestData.tags], + hypervisor=primarystorage[TestData.hypervisor] + ) + except Exception as e: + cls.skip_reason = ( + "Could not register the Linstor primary storage pool (%s). If the Linstor controller " + "requires authentication, set the LINSTOR_API_TOKEN env var to a valid controller API " + "token before running these tests." % e) + return + + # Compute offering with encrypted root, pinned to the Linstor pool via the storage tag. + cls.compute_offering_encrypted = ServiceOffering.create( + cls.apiClient, + cls.testdata[TestData.computeOffering], + encryptroot=True + ) + + cls.account = Account.create(cls.apiClient, cls.testdata[TestData.account], admin=1) + cls.user = User.create( + cls.apiClient, cls.testdata[TestData.user], + account=cls.account.name, domainid=cls.domain.id) + + cls._cleanup = [ + cls.compute_offering_encrypted, + cls.user, + cls.account, + ] + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiClient, cls._cleanup) + if getattr(cls, "primary_storage", None) is not None: + cls.primary_storage.delete(cls.apiClient) + except Exception as e: + logging.debug("Exception in tearDownClass: %s" % e) + + def setUp(self): + if self.skip_reason: + self.skipTest(self.skip_reason) + self.cleanup = [] + + def tearDown(self): + cleanup_resources(self.apiClient, self.cleanup) + + # --------------------------------------------------------------------- # + # Tests + # --------------------------------------------------------------------- # + + @attr(tags=['basic'], required_hardware=True) + def test_01_revert_encrypted_root_snapshot(self): + """Snapshot an encrypted root volume, change it, revert, and verify the data and boot.""" + vm = self._deploy_encrypted_vm("TestEncVM-revert") + + # 1. write a marker into the encrypted root volume + self._write_marker(vm, "linstor-encrypted-v1") + + # 2. snapshot the (stopped) root volume -> encrypted qcow2 on secondary + vm.stop(self.apiClient) + snapshot = self._snapshot_root_volume(vm) + + # 3. change the data so a successful revert is detectable + self._start_vm(vm) + self._write_marker(vm, "linstor-encrypted-v2-CHANGED") + + # 4. revert the volume to the snapshot (requires the VM stopped) + vm.stop(self.apiClient) + Volume.revertToSnapshot(self.apiClient, snapshot.id) + + # 5. the VM must boot again and the original data must be back + self._start_vm(vm) + restored = self._read_marker(vm) + self.assertEqual( + "linstor-encrypted-v1", restored, + "Reverted encrypted root volume has wrong content (got %r) - decryption/round-trip broken" % restored + ) + + @attr(tags=['basic'], required_hardware=True) + def test_02_create_volume_from_encrypted_snapshot_is_rejected(self): + """Creating a new volume from an encrypted volume's snapshot must be rejected by CloudStack. + + CloudStack core (VolumeApiServiceImpl) unconditionally blocks this for any encrypted source + volume ("Cannot create new volumes from encrypted volume snapshots"), so the request must never + reach the storage layer. This is a guard test: if the limitation is ever lifted, decryption + support for the create-from-snapshot path (KVMStorageProcessor / LinstorStorageAdaptor) must be + added and this test updated accordingly. + """ + vm = self._deploy_encrypted_vm("TestEncVM-create") + + self._write_marker(vm, "linstor-encrypted-create-src") + vm.stop(self.apiClient) + snapshot = self._snapshot_root_volume(vm) + + cmd = createVolume.createVolumeCmd() + cmd.name = "%s-%d" % (self.testdata[TestData.diskName], random.randint(0, 100000)) + cmd.zoneid = self.zone.id + cmd.account = self.account.name + cmd.domainid = self.domain.id + cmd.snapshotid = snapshot.id + + try: + self.apiClient.createVolume(cmd) + self.fail("Creating a volume from an encrypted volume snapshot should have been rejected") + except CloudstackAPIException as e: + self.assertIn( + "encrypted volume snapshots", str(e), + "Unexpected error creating volume from encrypted snapshot: %s" % e + ) + + @attr(tags=['basic'], required_hardware=True) + def test_03_backed_up_snapshot_qcow2_is_encrypted(self): + """The qcow2 written to secondary storage for an encrypted volume must itself be LUKS encrypted.""" + if not self.hostConfig: + self.skipTest("No host SSH credentials available (set HOST_SSH_USER/HOST_SSH_PASSWORD or " + "provide them in the marvin config) - cannot inspect the secondary-storage qcow2") + vm = self._deploy_encrypted_vm("TestEncVM-atrest") + self._write_marker(vm, "linstor-encrypted-atrest") + vm.stop(self.apiClient) + snapshot = self._snapshot_root_volume(vm) + + info = self._qemu_img_info_of_backed_up_snapshot(snapshot) + if info is None: + self.skipTest("Could not locate the backed-up snapshot on secondary storage to inspect it") + + encrypted = bool(info.get("encrypted")) or "encrypt" in json.dumps(info.get("format-specific", {})) + self.assertTrue( + encrypted, + "Backed-up snapshot qcow2 is NOT encrypted at rest: %s" % json.dumps(info) + ) + + # --------------------------------------------------------------------- # + # Helpers + # --------------------------------------------------------------------- # + + def _deploy_encrypted_vm(self, name): + vm = VirtualMachine.create( + self.apiClient, + {"name": name, "displayname": name}, + accountid=self.account.name, + zoneid=self.zone.id, + serviceofferingid=self.compute_offering_encrypted.id, + templateid=self.template.id, + domainid=self.domain.id, + startvm=False, + mode='basic', + ) + self.cleanup.insert(0, vm) + self._start_vm(vm) + return vm + + def _snapshot_root_volume(self, vm): + root = list_volumes(self.apiClient, virtualmachineid=vm.id, type="ROOT", listall=True)[0] + snapshot = Snapshot.create( + self.apiClient, + volume_id=root.id, + account=self.account.name, + domainid=self.domain.id, + ) + self.assertIsNotNone(snapshot, "Could not create snapshot of encrypted root volume") + self.cleanup.insert(0, snapshot) + return snapshot + + def _vm_ssh(self, vm): + # The VM is deployed stopped, so its instance has no ssh_ip yet; the IP may also change across + # stop/start cycles. Always pass the current address from a fresh lookup. + ipaddress = self._get_vm(vm.id).ipaddress + return vm.get_ssh_client(ipaddress=ipaddress, reconnect=True, retries=5) + + def _write_marker(self, vm, content): + ssh = self._vm_ssh(vm) + ssh.execute("echo '%s' > %s" % (content, MARKER_PATH)) + ssh.execute("sync") + + def _read_marker(self, vm): + ssh = self._vm_ssh(vm) + result = ssh.execute("cat %s" % MARKER_PATH) + return result[0].strip() if result else None + + @classmethod + def _encryption_capable_host_exists(cls): + hosts = Host.list(cls.apiClient, zoneid=cls.zone.id, type='Routing', hypervisor='KVM', state='Up') + return any(getattr(h, "encryptionsupported", False) for h in (hosts or [])) + + @classmethod + def _get_vm(cls, vm_id): + return list_virtual_machines(cls.apiClient, id=vm_id)[0] + + @classmethod + def _start_vm(cls, vm): + vm_for_check = cls._get_vm(vm.id) + if vm_for_check.state == VirtualMachine.STOPPED: + vm.start(cls.apiClient) + vm_for_check = cls._get_vm(vm.id) + ServiceReady.wait_ssh_ready(vm_for_check.ipaddress) + return vm_for_check + + def _host_ssh(self): + host = list_hosts(self.apiClient, type='Routing', hypervisor='KVM', state='Up')[0] + return SshClient( + host=host.ipaddress, port=22, + user=self.hostConfig['username'], passwd=self.hostConfig['password']) + + def _qemu_img_info_of_backed_up_snapshot(self, snapshot): + """Self-mount the secondary NFS export on a host and run 'qemu-img info' on the snapshot file.""" + # The backed-up snapshot's physical path on secondary storage isn't exposed via the API, so we + # read it from the DB. The DB may be unreachable from where the tests run (e.g. MariaDB bound to + # localhost on the management server); in that case return None so the test skips. + try: + rows = self.dbConnection.execute( + "SELECT ss.install_path " + "FROM snapshot_store_ref ss JOIN snapshots s ON s.id = ss.snapshot_id " + "WHERE s.uuid = '%s' AND ss.store_role = 'Image'" % snapshot.id) + store = self.dbConnection.execute( + "SELECT url FROM image_store WHERE role = 'Image' AND removed IS NULL LIMIT 1") + except Exception as e: + logging.debug("DB lookup for snapshot install path failed: %s" % e) + return None + + if not rows or not rows[0][0] or not store or not store[0][0]: + return None + install_path = rows[0][0] + url = store[0][0] # e.g. nfs:/// + if not url.startswith("nfs://"): + return None + server, export = url[len("nfs://"):].split("/", 1) + + ssh = self._host_ssh() + mount_point = "/tmp/cs_sectest_%d" % random.randint(0, 100000) + try: + ssh.execute("mkdir -p %s" % mount_point) + ssh.execute("mount -t nfs -o ro %s:/%s %s" % (server, export, mount_point)) + out = ssh.execute("qemu-img info --output=json %s/%s" % (mount_point, install_path)) + return json.loads("".join(out)) if out else None + except Exception as e: + logging.debug("qemu-img info on secondary failed: %s" % e) + return None + finally: + ssh.execute("umount %s 2>/dev/null; rmdir %s 2>/dev/null" % (mount_point, mount_point)) diff --git a/test/integration/plugins/linstor/test_linstor_volumes.py b/test/integration/plugins/linstor/test_linstor_volumes.py index e0ba15a0499c..c2c220bfe9e9 100644 --- a/test/integration/plugins/linstor/test_linstor_volumes.py +++ b/test/integration/plugins/linstor/test_linstor_volumes.py @@ -16,6 +16,7 @@ # under the License. import logging +import os import random import time import socket @@ -283,6 +284,17 @@ def setUpClass(cls): cls.testdata = TestData(first_host.ipaddress).testdata + # Registering a Linstor pool makes the management server read the resource-group capacity from + # the controller. If the controller enforces authentication, that call needs an API token, + # supplied as the 'lin.auth.apitoken' add-pool detail. Provide it via LINSTOR_API_TOKEN so it is + # never hard-coded; leave it unset for an unauthenticated controller. + api_token = os.environ.get("LINSTOR_API_TOKEN") + if api_token: + for storage_key in (TestData.primaryStorage, + TestData.primaryStorageSameInstance, + TestData.primaryStorageDistinctInstance): + cls.testdata[storage_key]["details"]["lin.auth.apitoken"] = api_token + # Get Resources from Cloud Infrastructure cls.zone = get_zone(cls.apiClient, zone_id=cls.testdata[TestData.zoneId]) cls.cluster = list_clusters(cls.apiClient)[0] 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/plugins/scaleio/README.md b/test/integration/plugins/scaleio/README.md index 6e1212ecb3a1..47d458d34f8e 100644 --- a/test/integration/plugins/scaleio/README.md +++ b/test/integration/plugins/scaleio/README.md @@ -1,9 +1,28 @@ + + # PowerFlex/ScaleIO storage plugin -================================== + This directory contains the basic VM, Volume life cycle tests for PowerFlex/ScaleIO storage pool (in KVM hypervisor). -# Running tests -=============== +## Running tests + To run the basic volume tests, first update the below test data of the CloudStack environment ``` diff --git a/test/integration/plugins/storpool/MigrateVolumeToStorPool.py b/test/integration/plugins/storpool/MigrateVolumeToStorPool.py index 5babdca094e8..85817902b021 100644 --- a/test/integration/plugins/storpool/MigrateVolumeToStorPool.py +++ b/test/integration/plugins/storpool/MigrateVolumeToStorPool.py @@ -84,6 +84,12 @@ def setUpCloudStack(cls): zone = config.zones[0] assert zone is not None + td = TestData() + cls.testdata = td.testdata + cls.helper = StorPoolHelper() + sp_pools = cls.helper.get_pool(zone) + assert sp_pools is not None + cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, auth=zone.spAuthToken, multiCluster=True) testClient = super(TestMigrateVolumeToAnotherPool, cls).getClsTestClient() cls.apiclient = testClient.getApiClient() @@ -103,11 +109,8 @@ def setUpCloudStack(cls): # Get Zone, Domain and templates cls.domain = get_domain(cls.apiclient) - td = TestData() - cls.testdata = td.testdata - cls.helper = StorPoolHelper() - storpool_primary_storage = cls.testdata[TestData.primaryStorage] - cls.template_name = storpool_primary_storage.get("name") + storpool_primary_storage = sp_pools[0] + cls.template_name = storpool_primary_storage["name"] storpool_service_offerings = cls.testdata[TestData.serviceOffering] nfs_service_offerings = cls.testdata[TestData.serviceOfferingsPrimary] @@ -283,12 +286,6 @@ def tearDownClass(cls): @classmethod def cleanUpCloudStack(cls): try: - if cls.nfs_storage_pool.state is not "Maintenance": - cls.nfs_storage_pool = StoragePool.enableMaintenance(cls.apiclient, cls.nfs_storage_pool.id) - - if cls.ceph_storage_pool.state is not "Maintenance": - cls.ceph_storage_pool = StoragePool.enableMaintenance(cls.apiclient, cls.ceph_storage_pool.id) - cls.storage_pool = StoragePool.update(cls.apiclient, id=cls.storage_pool.id, tags = ["ssd"]) @@ -443,5 +440,5 @@ def test_3_migrate_volume_from_nfs_to_storpool(self): self.assertEqual( self.random_data_0, result[0], - "Check the random data is equal with the ramdom file!" + "Check the random data is equal with the random file!" ) diff --git a/test/integration/plugins/storpool/TestEncryptedVolumes.py b/test/integration/plugins/storpool/TestEncryptedVolumes.py index eed64950ef51..e77cd0bd0945 100644 --- a/test/integration/plugins/storpool/TestEncryptedVolumes.py +++ b/test/integration/plugins/storpool/TestEncryptedVolumes.py @@ -97,9 +97,18 @@ def setUpCloudStack(cls): zone = config.zones[0] assert zone is not None - cls.zone = list_zones(cls.apiclient, name=zone.name)[0] - cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + storage_pools = zone.primaryStorages + sp_pools = [] + for storage in storage_pools: + if storage['provider'] and "StorPool" in storage['provider']: + sp_pools.append(storage) + + if len(sp_pools) < 2: + cls.debug("Cannot perform the tests because there aren't the required count of StorPool storage pools %s" % sp_pools) + return + + cls.zone = list_zones(cls.apiclient, name=zone.name)[0] cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, auth=zone.spAuthToken, multiCluster=True) cls.helper = StorPoolHelper() @@ -118,124 +127,56 @@ def setUpCloudStack(cls): td = TestData() cls.testdata = td.testdata - cls.sp_template_1 = "ssd" - storpool_primary_storage = { - "name": cls.sp_template_1, - "zoneid": cls.zone.id, - "url": "SP_API_HTTP=%s:%s;SP_AUTH_TOKEN=%s;SP_TEMPLATE=%s" % (zone.spEndpoint, zone.spEndpointPort, zone.spAuthToken, cls.sp_template_1), - "scope": "zone", - "capacitybytes": 564325555333, - "capacityiops": 155466, - "hypervisor": "kvm", - "provider": "StorPool", - "tags": cls.sp_template_1 - } - - cls.storpool_primary_storage = storpool_primary_storage + storpool_primary_storage = sp_pools[0] + cls.template_name = storpool_primary_storage["name"] storage_pool = list_storage_pools( cls.apiclient, - name=storpool_primary_storage["name"] - ) - + name=cls.template_name + ) if storage_pool is None: - newTemplate = sptypes.VolumeTemplateCreateDesc(name=storpool_primary_storage["name"], placeAll="virtual", - placeTail="virtual", placeHead="virtual", replication=1) - template_on_local = cls.spapi.volumeTemplateCreate(newTemplate) - storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) else: storage_pool = storage_pool[0] - cls.primary_storage = storage_pool + cls.storage_pool = storage_pool + cls.helper.updateStoragePoolTags(cls.apiclient, cls.storage_pool.id, cls.testdata[TestData.sp_template_1]["tags"]) - storpool_service_offerings_ssd = { - "name": "ssd-encrypted", - "displaytext": "SP_CO_2 (Min IOPS = 10,000; Max IOPS = 15,000)", - "cpunumber": 1, - "cpuspeed": 500, - "memory": 512, - "storagetype": "shared", - "customizediops": False, - "hypervisorsnapshotreserve": 200, - "encryptroot": True, - "tags": cls.sp_template_1 - } + cls.debug(pprint.pformat(storage_pool)) - service_offerings_ssd = list_service_offering( - cls.apiclient, - name=storpool_service_offerings_ssd["name"] - ) + cls.primary_storage = storage_pool - if service_offerings_ssd is None: - service_offerings_ssd = ServiceOffering.create(cls.apiclient, storpool_service_offerings_ssd, encryptroot=True) - else: - service_offerings_ssd = service_offerings_ssd[0] + storpool_primary_storage = sp_pools[1] + cls.template_name = storpool_primary_storage["name"] - cls.service_offering = service_offerings_ssd - cls.debug(pprint.pformat(cls.service_offering)) - - cls.sp_template_2 = "ssd2" - - storpool_primary_storage2 = { - "name": cls.sp_template_2, - "zoneid": cls.zone.id, - "url": "SP_API_HTTP=%s:%s;SP_AUTH_TOKEN=%s;SP_TEMPLATE=%s" % (zone.spEndpoint, zone.spEndpointPort, zone.spAuthToken, cls.sp_template_2), - "scope": "zone", - "capacitybytes": 564325555333, - "capacityiops": 1554, - "hypervisor": "kvm", - "provider": "StorPool", - "tags": cls.sp_template_2 - } - - cls.storpool_primary_storage2 = storpool_primary_storage2 storage_pool = list_storage_pools( cls.apiclient, - name=storpool_primary_storage2["name"] - ) - + name=cls.template_name + ) if storage_pool is None: - newTemplate = sptypes.VolumeTemplateCreateDesc(name=storpool_primary_storage2["name"], placeAll="virtual", - placeTail="virtual", placeHead="virtual", replication=1) - template_on_local = cls.spapi.volumeTemplateCreate(newTemplate) - storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage2) - + storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) else: storage_pool = storage_pool[0] - cls.primary_storage2 = storage_pool + cls.storage_pool = storage_pool + cls.helper.updateStoragePoolTags(cls.apiclient, cls.storage_pool.id, cls.testdata[TestData.sp_template_2]["tags"]) - storpool_service_offerings_ssd2 = { - "name": "ssd2-encrypted", - "displaytext": "SP_CO_2", - "cpunumber": 1, - "cpuspeed": 500, - "memory": 512, - "storagetype": "shared", - "customizediops": False, - "encryptroot": True, - "tags": cls.sp_template_2 - } - - service_offerings_ssd2 = list_service_offering( - cls.apiclient, - name=storpool_service_offerings_ssd2["name"] - ) + cls.debug(pprint.pformat(storage_pool)) - if service_offerings_ssd2 is None: - service_offerings_ssd2 = ServiceOffering.create(cls.apiclient, storpool_service_offerings_ssd2, encryptroot=True) - else: - service_offerings_ssd2 = service_offerings_ssd2[0] + cls.primary_storage2 = storage_pool - cls.service_offering2 = service_offerings_ssd2 + storpool_service_offerings_ssd = cls.testdata[TestData.serviceOfferingEncrypted] - cls.disk_offerings_ssd2_encrypted = list_disk_offering( + service_offerings_ssd = list_service_offering( cls.apiclient, - name=cls.testdata[TestData.diskOfferingEncrypted2]["name"] + name=storpool_service_offerings_ssd["name"] ) - if cls.disk_offerings_ssd2_encrypted is None: - cls.disk_offerings_ssd2_encrypted = DiskOffering.create(cls.apiclient, cls.testdata[TestData.diskOfferingEncrypted2], encrypt=True) + + if service_offerings_ssd is None: + service_offerings_ssd = ServiceOffering.create(cls.apiclient, storpool_service_offerings_ssd, encryptroot=True) else: - cls.disk_offerings_ssd2_encrypted = cls.disk_offerings_ssd2_encrypted[0] + service_offerings_ssd = service_offerings_ssd[0] + + cls.service_offering = service_offerings_ssd + cls.debug(pprint.pformat(cls.service_offering)) cls.disk_offering_ssd_encrypted = list_disk_offering( cls.apiclient, @@ -531,7 +472,7 @@ def test_03_revert_vm_snapshots(self): self.assertEqual( self.random_data_0, result[0], - "Check the random data is equal with the ramdom file!" + "Check the random data is equal with the random file!" ) # Delete VM snapshot diff --git a/test/integration/plugins/storpool/TestStorPoolVolumes.py b/test/integration/plugins/storpool/TestStorPoolVolumes.py index 70f8900df58b..a6b81216f5e6 100644 --- a/test/integration/plugins/storpool/TestStorPoolVolumes.py +++ b/test/integration/plugins/storpool/TestStorPoolVolumes.py @@ -32,6 +32,7 @@ Volume, SecurityGroup, Role, + DiskOffering, ) from marvin.lib.common import (get_zone, get_domain, @@ -79,17 +80,24 @@ def setUpClass(cls): def setUpCloudStack(cls): config = cls.getClsConfig() StorPoolHelper.logger = cls + cls.logger = StorPoolHelper.logger zone = config.zones[0] assert zone is not None + td = TestData() + cls.testdata = td.testdata + cls.helper = StorPoolHelper() + + sp_pools = cls.helper.get_pool(zone) + assert sp_pools is not None + cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, auth=zone.spAuthToken, multiCluster=True) testClient = super(TestStoragePool, cls).getClsTestClient() cls._cleanup = [] cls.apiclient = testClient.getApiClient() - cls.helper = StorPoolHelper() cls.unsupportedHypervisor = False cls.hypervisor = testClient.getHypervisorInfo() @@ -106,34 +114,11 @@ def setUpCloudStack(cls): cls.debug(list_zones(cls.apiclient, name=zone.name)) assert cls.zone is not None - cls.sp_template_1 = "ssd" - storpool_primary_storage = { - "name" : cls.sp_template_1, - "zoneid": cls.zone.id, - "url": "SP_API_HTTP=%s:%s;SP_AUTH_TOKEN=%s;SP_TEMPLATE=%s" % (zone.spEndpoint, zone.spEndpointPort, zone.spAuthToken, cls.sp_template_1), - "scope": "zone", - "capacitybytes": 564325555333, - "capacityiops": 155466, - "hypervisor": "kvm", - "provider": "StorPool", - "tags": cls.sp_template_1 - } + cls.sp_template_1 = cls.testdata[TestData.sp_template_1]["tags"] + storpool_primary_storage = sp_pools[0] - cls.storpool_primary_storage = storpool_primary_storage - - storage_pool = list_storage_pools( - cls.apiclient, - name=storpool_primary_storage["name"] - ) - - if storage_pool is None: - newTemplate = sptypes.VolumeTemplateCreateDesc(name = storpool_primary_storage["name"],placeAll = "virtual", placeTail = "virtual", placeHead = "virtual", replication=1) - template_on_local = cls.spapi.volumeTemplateCreate(newTemplate) - - storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) - else: - storage_pool = storage_pool[0] - cls.primary_storage = storage_pool + cls.primary_storage = cls.create_pool_if_not_exists(storpool_primary_storage) + cls.helper.updateStoragePoolTags(cls.apiclient, cls.primary_storage.id, cls.sp_template_1) storpool_service_offerings_ssd = { @@ -150,7 +135,7 @@ def setUpCloudStack(cls): service_offerings_ssd = list_service_offering( cls.apiclient, - name=storpool_service_offerings_ssd["name"] + name=cls.sp_template_1 ) if service_offerings_ssd is None: @@ -162,36 +147,12 @@ def setUpCloudStack(cls): cls.debug(pprint.pformat(cls.service_offering)) - cls.sp_template_2 = "ssd2" - - storpool_primary_storage2 = { - "name" : cls.sp_template_2, - "zoneid": cls.zone.id, - "url": "SP_API_HTTP=%s:%s;SP_AUTH_TOKEN=%s;SP_TEMPLATE=%s" % (zone.spEndpoint, zone.spEndpointPort, zone.spAuthToken, cls.sp_template_2), - "scope": "zone", - "capacitybytes": 564325555333, - "capacityiops": 1554, - "hypervisor": "kvm", - "provider": "StorPool", - "tags": cls.sp_template_2 - } - - cls.storpool_primary_storage2 = storpool_primary_storage2 - storage_pool = list_storage_pools( - cls.apiclient, - name=storpool_primary_storage2["name"] - ) - - if storage_pool is None: - newTemplate = sptypes.VolumeTemplateCreateDesc(name = storpool_primary_storage2["name"],placeAll = "virtual", placeTail = "virtual", placeHead = "virtual", replication=1) - - template_on_local = cls.spapi.volumeTemplateCreate(newTemplate) + cls.sp_template_2 = cls.testdata[TestData.sp_template_2]["tags"] - storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage2) + storpool_primary_storage2 = sp_pools[1] - else: - storage_pool = storage_pool[0] - cls.primary_storage2 = storage_pool + cls.primary_storage2 = cls.create_pool_if_not_exists(storpool_primary_storage2) + cls.helper.updateStoragePoolTags(cls.apiclient, cls.primary_storage2.id, cls.sp_template_2) storpool_service_offerings_ssd2 = { "name": cls.sp_template_2, @@ -216,25 +177,11 @@ def setUpCloudStack(cls): cls.service_offering2 = service_offerings_ssd2 - disk_offerings = list_disk_offering( - cls.apiclient, - name="Small" - ) - - disk_offering_20 = list_disk_offering( - cls.apiclient, - name="Medium" - ) + cls.disk_offerings = cls.create_do_if_not_exists(cls.testdata[TestData.diskOfferingSmall]) - disk_offering_100 = list_disk_offering( - cls.apiclient, - name="Large" - ) + cls.disk_offering_20 = cls.create_do_if_not_exists(cls.testdata[TestData.diskOfferingMedium]) - - cls.disk_offerings = disk_offerings[0] - cls.disk_offering_20 = disk_offering_20[0] - cls.disk_offering_100 = disk_offering_100[0] + cls.disk_offering_100 = cls.create_do_if_not_exists(cls.testdata[TestData.diskOfferingLarge]) #The version of CentOS has to be supported template = get_template( @@ -253,14 +200,14 @@ def setUpCloudStack(cls): cls.services["zoneid"] = cls.zone.id cls.services["diskofferingid"] = cls.disk_offerings.id - role = Role.list(cls.apiclient, name='Admin') + role = Role.list(cls.apiclient, name='Root Admin') # Create VMs, VMs etc cls.account = Account.create( cls.apiclient, cls.services["account"], domainid=cls.domain.id, - roleid = role[0].id + roleid = 1 ) securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0] @@ -271,7 +218,7 @@ def setUpCloudStack(cls): cls.apiclient, {"diskname":"StorPoolDisk-1" }, zoneid=cls.zone.id, - diskofferingid=disk_offerings[0].id, + diskofferingid=cls.disk_offerings.id, account=cls.account.name, domainid=cls.account.domainid, ) @@ -280,7 +227,7 @@ def setUpCloudStack(cls): cls.apiclient, {"diskname":"StorPoolDisk-2" }, zoneid=cls.zone.id, - diskofferingid=disk_offerings[0].id, + diskofferingid=cls.disk_offerings.id, account=cls.account.name, domainid=cls.account.domainid, ) @@ -289,7 +236,7 @@ def setUpCloudStack(cls): cls.apiclient, {"diskname":"StorPoolDisk-3" }, zoneid=cls.zone.id, - diskofferingid=disk_offerings[0].id, + diskofferingid=cls.disk_offerings.id, account=cls.account.name, domainid=cls.account.domainid, ) @@ -337,6 +284,34 @@ def setUpCloudStack(cls): cls.random_data = "random.data" return + @classmethod + def create_do_if_not_exists(cls, data): + disk_offerings = list_disk_offering( + cls.apiclient, + name=data["name"] + ) + if disk_offerings is None: + disk_offerings = DiskOffering.create(cls.apiclient, data) + else: + disk_offerings = disk_offerings[0] + return disk_offerings + + @classmethod + def create_pool_if_not_exists(cls, storpool_primary_storage): + storage_pool = list_storage_pools( + cls.apiclient, + name=storpool_primary_storage["name"] + ) + if storage_pool is None: + newTemplate = sptypes.VolumeTemplateCreateDesc(name=storpool_primary_storage["name"], placeAll="virtual", + placeTail="virtual", placeHead="virtual", replication=1) + template_on_local = cls.spapi.volumeTemplateCreate(newTemplate) + + storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) + else: + storage_pool = storage_pool[0] + return storage_pool + @classmethod def tearDownClass(cls): cls.cleanUpCloudStack() @@ -406,6 +381,8 @@ def test_01_snapshot_to_template(self): self.assertIsNotNone(template, "Template is None") self.assertIsInstance(template, Template, "Template is instance of template") self._cleanup.append(template) + virtual_machine.stop(self.apiclient, forced=True) + virtual_machine.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_02_snapshot_to_template_bypass_secondary(self): @@ -487,6 +464,8 @@ def test_02_snapshot_to_template_bypass_secondary(self): self.assertIsNotNone(template, "Template is None") self.assertIsInstance(template, Template, "Template is instance of template") self._cleanup.append(template) + virtual_machine.stop(self.apiclient, forced=True) + virtual_machine.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_03_snapshot_volume_with_secondary(self): @@ -1016,6 +995,8 @@ def test_09_vm_from_bypassed_template(self): ) ssh_client = vm.get_ssh_client(reconnect=True) + vm.stop(self.apiclient, forced=True) + vm.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") @@ -1155,7 +1136,7 @@ def test_11_revert_vm_snapshots(self): self.assertEqual( self.random_data_0, result[0], - "Check the random data is equal with the ramdom file!" + "Check the random data is equal with the random file!" ) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") @@ -1825,6 +1806,8 @@ def test_22_snapshot_root_disk(self): self.assertIsNotNone(snapshot, "Snapshot is None") self.assertEqual(list_volumes_of_vm[0].id, snapshot.volumeid, "Snapshot is not for the same volume") + vm.stop(self.apiclient, forced=True) + vm.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") @@ -1860,6 +1843,8 @@ def test_23_volume_to_template(self): self.assertIsNotNone(template, "Template is None") self.assertIsInstance(template, Template, "Template is instance of template") self._cleanup.append(template) + virtual_machine.stop(self.apiclient, forced=True) + virtual_machine.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_24_migrate_vm_to_another_storage(self): @@ -1892,6 +1877,8 @@ def test_25_migrate_volume_to_another_storage(self): self.assertFalse(hasattr(self.volume, 'virtualmachineid') , "Volume is not detached") self.assertFalse(hasattr(self.volume, 'storageid') , "Volume is not detached") + + self.helper.updateStoragePoolTags(apiclient=self.apiclient, poolId=self.primary_storage2.id, tags=self.testdata[TestData.sp_template_1]["tags"]) volume = Volume.migrate( self.apiclient, volumeid = self.volume.id, @@ -1901,6 +1888,8 @@ def test_25_migrate_volume_to_another_storage(self): self.assertIsNotNone(volume, "Volume is None") self.assertEqual(volume.storageid, self.primary_storage2.id, "Storage is the same") + self.helper.updateStoragePoolTags(apiclient=self.apiclient, poolId=self.primary_storage2.id, tags=self.testdata[TestData.sp_template_2]["tags"]) + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_26_create_vm_on_another_storpool_storage(self): @@ -1916,6 +1905,8 @@ def test_26_create_vm_on_another_storpool_storage(self): rootdisksize=10 ) self.assertIsNotNone(virtual_machine, "Could not create virtual machine on another Storpool primary storage") + virtual_machine.stop(self.apiclient, forced=True) + virtual_machine.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") @@ -1958,6 +1949,8 @@ def test_27_snapshot_to_volume_of_root_disk(self): self.assertIsNotNone(volume, "Could not create volume from snapshot") self.assertIsInstance(volume, Volume, "Volume is not instance of Volume") + virtual_machine.stop(self.apiclient, forced=True) + virtual_machine.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_28_download_volume(self): @@ -2041,6 +2034,10 @@ def test_29_create_vm_from_template_not_on_storpool(self): self.assertIsNotNone(template, "Template is None") self.assertIsInstance(template, Template, "Template is instance of template") self._cleanup.append(template) + virtual_machine.stop(self.apiclient, forced=True) + virtual_machine.delete(self.apiclient, expunge=True) + virtual_machine2.stop(self.apiclient, forced=True) + virtual_machine2.delete(self.apiclient, expunge=True) @classmethod def create_volume(self, apiclient, zoneid=None, snapshotid=None, account=None, domainid=None): diff --git a/test/integration/plugins/storpool/TestTagsOnStorPool.py b/test/integration/plugins/storpool/TestTagsOnStorPool.py index a66fb3570f42..33ad41a463b5 100644 --- a/test/integration/plugins/storpool/TestTagsOnStorPool.py +++ b/test/integration/plugins/storpool/TestTagsOnStorPool.py @@ -44,6 +44,7 @@ VmSnapshot, Volume, SecurityGroup, + DiskOffering, ) from marvin.lib.common import (get_zone, get_domain, @@ -79,10 +80,16 @@ def setUpClass(cls): def setUpCloudStack(cls): config = cls.getClsConfig() StorPoolHelper.logger = cls + td = TestData() + cls.testdata = td.testdata + cls.helper = StorPoolHelper() zone = config.zones[0] assert zone is not None + sp_pools = cls.helper.get_pool(zone) + assert sp_pools is not None + cls.spapi = spapi.Api(host=zone.spEndpoint, port=zone.spEndpointPort, auth=zone.spAuthToken, multiCluster=True) testClient = super(TestStoragePool, cls).getClsTestClient() cls.apiclient = testClient.getApiClient() @@ -98,16 +105,9 @@ def setUpCloudStack(cls): # Get Zone, Domain and templates cls.domain = get_domain(cls.apiclient) cls.zone = list_zones(cls.apiclient, name=zone.name)[0] - cls.debug(cls.zone) - cls.debug(list_zones(cls.apiclient, name=zone.name)) - assert cls.zone is not None assert cls.zone is not None - td = TestData() - cls.testdata = td.testdata - cls.helper = StorPoolHelper() - cls.account = cls.helper.create_account( cls.apiclient, cls.services["account"], @@ -120,34 +120,39 @@ def setUpCloudStack(cls): securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0] cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id) - storpool_primary_storage = cls.testdata[TestData.primaryStorage] - - storpool_service_offerings = cls.testdata[TestData.serviceOffering] - - cls.template_name = storpool_primary_storage.get("name") - + storpool_primary_storage = sp_pools[0] + cls.template_name = storpool_primary_storage["name"] storage_pool = list_storage_pools( cls.apiclient, name=cls.template_name ) + if storage_pool is None: + storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) + else: + storage_pool = storage_pool[0] + cls.storage_pool = storage_pool + cls.helper.updateStoragePoolTags(cls.apiclient, cls.storage_pool.id, cls.testdata[TestData.sp_template_1]["tags"]) - service_offerings = list_service_offering( + cls.debug(pprint.pformat(storage_pool)) + + + disk_offerings = list_disk_offering( cls.apiclient, name=cls.template_name ) + if disk_offerings is None: + offering = cls.testdata[TestData.diskOfferingCustom] + cls.disk_offerings = DiskOffering.create(cls.apiclient, services=offering, custom=True) + else: + cls.disk_offerings = disk_offerings[0] - disk_offerings = list_disk_offering( + storpool_service_offerings = cls.testdata[TestData.serviceOffering] + + service_offerings = list_service_offering( cls.apiclient, - name="ssd" + name=cls.template_name ) - cls.disk_offerings = disk_offerings[0] - if storage_pool is None: - storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) - else: - storage_pool = storage_pool[0] - cls.storage_pool = storage_pool - cls.debug(pprint.pformat(storage_pool)) if service_offerings is None: service_offerings = ServiceOffering.create(cls.apiclient, storpool_service_offerings) else: @@ -283,7 +288,7 @@ def test_01_set_vcpolicy_tag_to_vm_with_attached_disks(self): virtualmachineid = self.virtual_machine.id, listall=True ) - self.vc_policy_tags(volumes, vm_tags, vm, True) + self.vc_policy_tags(volumes, vm_tags, vm, should_tags_exists=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") @@ -323,7 +328,7 @@ def test_03_create_vm_snapshot_vc_policy_tag(self): vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True) vm_tags = vm[0].tags - self.vc_policy_tags(volumes, vm_tags, vm, True) + self.vc_policy_tags(volumes, vm_tags, vm, should_tags_exists=True) self.assertEqual(volume_attached.id, self.volume.id, "Is not the same volume ") @@ -455,12 +460,12 @@ def test_04_revert_vm_snapshots_vc_policy_tag(self): vm = list_virtual_machines(self.apiclient,id = self.virtual_machine.id, listall=True) vm_tags = vm[0].tags - self.vc_policy_tags(volumes, vm_tags, vm, True) + self.vc_policy_tags(volumes, vm_tags, vm, should_tags_exists=True) self.assertEqual( self.random_data_0, result[0], - "Check the random data is equal with the ramdom file!" + "Check the random data is equal with the random file!" ) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") @@ -491,14 +496,12 @@ def test_05_delete_vm_snapshots(self): list_snapshot_response = VmSnapshot.list( self.apiclient, - #vmid=self.virtual_machine.id, virtualmachineid=self.virtual_machine.id, listall=False) self.debug('list_snapshot_response -------------------- %s' % list_snapshot_response) self.assertIsNone(list_snapshot_response, "snapshot is already deleted") - @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_06_remove_vcpolicy_tag_when_disk_detached(self): """ Test remove vc-policy tag to disk detached from VM""" @@ -513,7 +516,7 @@ def test_06_remove_vcpolicy_tag_when_disk_detached(self): self.apiclient, self.volume_2 ) - self.vc_policy_tags( volumes, vm_tags, vm, False) + self.vc_policy_tags( volumes, vm_tags, vm, should_tags_exists=False) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_07_delete_vcpolicy_tag(self): @@ -550,7 +553,7 @@ def test_08_vcpolicy_tag_to_reverted_disk(self): virtualmachineid = self.virtual_machine2.id, listall=True, type = "ROOT" ) - self.vc_policy_tags(volume, vm_tags, vm, True) + self.vc_policy_tags(volume, vm_tags, vm, should_tags_exists=True) snapshot = Snapshot.create( self.apiclient, @@ -571,8 +574,8 @@ def test_08_vcpolicy_tag_to_reverted_disk(self): vm = list_virtual_machines(self.apiclient,id = self.virtual_machine2.id) vm_tags = vm[0].tags - vol = list_volumes(self.apiclient, id = snapshot.volumeid, listall=True) - self.vc_policy_tags(vol, vm_tags, vm, True) + vol = list_volumes(self.apiclient, id=snapshot.volumeid, listall=True) + self.vc_policy_tags(vol, vm_tags, vm, should_tags_exists=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_09_remove_vm_tags_on_datadisks_attached_to_destroyed_vm(self): @@ -586,38 +589,82 @@ def test_09_remove_vm_tags_on_datadisks_attached_to_destroyed_vm(self): vm_tags = vm[0].tags volumes = list_volumes( self.apiclient, - virtualmachineid = self.virtual_machine3.id, listall=True + virtualmachineid=self.virtual_machine3.id, listall=True ) - self.vc_policy_tags(volumes, vm_tags, vm, True) + self.vc_policy_tags(volumes, vm_tags, vm, should_tags_exists=True) volumes = list_volumes( self.apiclient, - virtualmachineid = self.virtual_machine3.id, listall=True, type="DATADISK" + virtualmachineid=self.virtual_machine3.id, listall=True, type="DATADISK" ) self.virtual_machine3.delete(self.apiclient, expunge=True) - self.vc_policy_tags(volumes, vm_tags, vm, False) + self.vc_policy_tags(volumes, vm_tags, vm, should_tags_exists=False) - def vc_policy_tags(self, volumes, vm_tags, vm, should_tags_exists=None): - vcPolicyTag = False - cvmTag = False + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_10_check_tags_on_deployed_vm_with_data_disk(self): + """ + Check disk and cvm tags are set on all volumes when VM is deployed with additional DATA disk + Detach the DATA disk + """ + vm = VirtualMachine.create( + self.apiclient, + {"name":"StorPool-%s" % uuid.uuid4() }, + zoneid=self.zone.id, + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + hypervisor=self.hypervisor, + diskofferingid=self.disk_offerings.id, + size=2, + rootdisksize=10 + ) + volumes = list_volumes( + self.apiclient, + virtualmachineid=vm.id, listall=True + ) + vm1 = list_virtual_machines(self.apiclient,id=vm.id, listall=True) + vm_tags = vm1[0].tags + self.vc_policy_tags(volumes, vm_tags, vm1, False, True) + vm.stop(self.apiclient, forced=True) + volumes = list_volumes( + self.apiclient, + virtualmachineid=vm.id, listall=True, type="DATADISK" + ) + + self.debug("detaching volume %s" % volumes) + VirtualMachine.detach_volume(vm, self.apiclient, volumes[0]) + self.vc_policy_tags(volumes, vm_tags, vm1, False, False) + + def vc_policy_tags(self, volumes, vm_tags, vm, tag_check=True, should_tags_exists=None,): + vc_policy_tag = False + cvm_tag = False + disk_id_tag = False for v in volumes: name = v.path.split("/")[3] - spvolume = self.spapi.volumeList(volumeName="~" + name) - tags = spvolume[0].tags + volume = self.spapi.volumeList(volumeName="~" + name) + tags = volume[0].tags + self.debug("Tags %s" % tags) for t in tags: for vm_tag in vm_tags: if t == vm_tag.key: - vcPolicyTag = True + vc_policy_tag = True self.assertEqual(tags[t], vm_tag.value, "Tags are not equal") - if t == 'cvm': - cvmTag = True - self.assertEqual(tags[t], vm[0].id, "CVM tag is not the same as vm UUID") - #self.assertEqual(tag.tags., second, msg) + if t == 'cvm': + cvm_tag = True + self.assertEqual(tags[t], vm[0].id, "CVM tag is not the same as vm UUID") + if t == 'disk': + disk_id_tag = True + self.assertEqual(tags[t], str(v.deviceid), "Disk tag is not equal to the device ID") if should_tags_exists: - self.assertTrue(vcPolicyTag, "There aren't volumes with vm tags") - self.assertTrue(cvmTag, "There aren't volumes with vm tags") + if tag_check: + self.assertTrue(vc_policy_tag, "There aren't volumes with vc policy tags") + self.assertTrue(cvm_tag, "There aren't volumes with vm UUID tags") + self.assertTrue(disk_id_tag, "There aren't volumes with vm disk tag") else: - self.assertFalse(vcPolicyTag, "The tags should be removed") - self.assertFalse(cvmTag, "The tags should be removed") + if tag_check: + self.assertFalse(vc_policy_tag, "The vc policy tag should be removed") + self.assertFalse(cvm_tag, "The cvm tag should be removed") + self.assertFalse(disk_id_tag, "The disk tag should be removed") diff --git a/test/integration/plugins/storpool/TestVmSnapshots.py b/test/integration/plugins/storpool/TestVmSnapshots.py index ab35c076b4e7..b0f8192f1bb3 100644 --- a/test/integration/plugins/storpool/TestVmSnapshots.py +++ b/test/integration/plugins/storpool/TestVmSnapshots.py @@ -29,6 +29,7 @@ User, Volume, SecurityGroup, + DiskOffering, ) from marvin.lib.common import (get_zone, get_domain, @@ -76,6 +77,8 @@ def setUpCloudStack(cls): cls.testdata = td.testdata cls.helper = StorPoolHelper() + sp_pools = cls.helper.get_pool(zone) + assert sp_pools is not None cls.services = testClient.getParsedTestDataConfig() # Get Zone, Domain and templates @@ -114,26 +117,29 @@ def setUpCloudStack(cls): securitygroup = SecurityGroup.list(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid)[0] cls.helper.set_securityGroups(cls.apiclient, account = cls.account.name, domainid= cls.account.domainid, id = securitygroup.id) - primarystorage = cls.testdata[TestData.primaryStorage] + primarystorage = sp_pools[0] serviceOffering = cls.testdata[TestData.serviceOffering] storage_pool = list_storage_pools( cls.apiclient, - name = primarystorage.get("name") + name = primarystorage["name"] ) cls.primary_storage = storage_pool[0] disk_offering = list_disk_offering( cls.apiclient, - name="ssd" + name=primarystorage["name"] ) - assert disk_offering is not None - + if disk_offering is None: + offering = cls.testdata[TestData.diskOfferingCustom] + cls.disk_offering = DiskOffering.create(cls.apiclient, services=offering, custom=True) + else: + cls.disk_offering = disk_offering[0] service_offering_only = list_service_offering( cls.apiclient, - name="ssd" + name=primarystorage["name"] ) if service_offering_only is not None: cls.service_offering_only = service_offering_only[0] @@ -143,8 +149,6 @@ def setUpCloudStack(cls): serviceOffering) assert cls.service_offering_only is not None - cls.disk_offering = disk_offering[0] - # Create 1 data volume_1 cls.volume = Volume.create( cls.apiclient, @@ -332,7 +336,7 @@ def test_02_revert_vm_snapshots(self): self.assertEqual( self.random_data_0, result[0], - "Check the random data is equal with the ramdom file!" + "Check the random data is equal with the random file!" ) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") diff --git a/test/integration/plugins/storpool/sp_util.py b/test/integration/plugins/storpool/sp_util.py index 70f36609af5c..084a57ee954e 100644 --- a/test/integration/plugins/storpool/sp_util.py +++ b/test/integration/plugins/storpool/sp_util.py @@ -84,6 +84,10 @@ class TestData(): diskOfferingTier1Template = "diskOfferingTier1Template" diskOfferingTier2Template = "diskOfferingTier2Template" diskOfferingWithTagsAndTempl = "diskOfferingWithTagsAndTempl" + diskOfferingSmall = "diskOfferingSmall" + diskOfferingMedium = "diskOfferingMedium" + diskOfferingLarge = "diskOfferingLarge" + diskOfferingCustom = "diskOfferingCustom" domainId = "domainId" hypervisor = "hypervisor" login = "login" @@ -100,6 +104,7 @@ class TestData(): serviceOfferingsPrimary = "serviceOfferingsPrimary" serviceOfferingsIops = "serviceOfferingsIops" serviceOfferingsCeph = "serviceOfferingsCeph" + serviceOfferingEncrypted = "serviceOfferingEncrypted" scope = "scope" StorPool = "StorPool" storageTag = ["ssd", "ssd2"] @@ -114,6 +119,8 @@ class TestData(): volume_6 = "volume_6" volume_7 = "volume_7" zoneId = "zoneId" + sp_template_1 = 'sp_template_1' + sp_template_2 = 'sp_template_2' def __init__(self): sp_template_1 = 'ssd' @@ -221,6 +228,18 @@ def __init__(self): "customizediops": True, "tags": sp_template_1, }, + TestData.serviceOfferingEncrypted: { + "name": "Test-encrypted", + "displaytext": "SP Encrypted", + "cpunumber": 1, + "cpuspeed": 500, + "memory": 512, + "storagetype": "shared", + "customizediops": False, + "hypervisorsnapshotreserve": 200, + "encryptroot": True, + "tags": sp_template_1 + }, TestData.diskOffering: { "name": "SP_DO_1", "displaytext": "SP_DO_1 (5GB Min IOPS = 300; Max IOPS = 500)", @@ -323,6 +342,38 @@ def __init__(self): TestData.tags: sp_template_1, "storagetype": "shared" }, + TestData.diskOfferingSmall: { + "name": "Test-Small", + "displaytext": "Small Disk Offering", + "disksize" : 5, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingMedium: { + "name": "Test-Medium", + "displaytext": "Medium Disk Offering", + "disksize": 20, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingLarge: { + "name": "Test-Large", + "displaytext": "Large Disk Offering", + "disksize": 100, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, + TestData.diskOfferingCustom: { + "name": "Test-Custom", + "displaytext": "Custom Disk Offering", + "custom": True, + "hypervisorsnapshotreserve": 200, + TestData.tags: sp_template_1, + "storagetype": "shared" + }, TestData.volume_1: { TestData.diskName: "test-volume-1", }, @@ -344,6 +395,12 @@ def __init__(self): TestData.volume_7: { TestData.diskName: "test-volume-7", }, + TestData.sp_template_1: { + "tags": "ssd" + }, + TestData.sp_template_2: { + "tags": "ssd2" + }, } class StorPoolHelper(): def setUpClass(cls): @@ -847,3 +904,80 @@ def getHostToDeployOrMigrate(cls, apiclient, hostsToavoid, clustersToDeploy): break return destinationHost + + @classmethod + def updateStoragePoolTags(cls, apiclient, poolId, tags): + StoragePool.update( + apiclient, + id=poolId, + tags=tags + ) + + @classmethod + def get_pool(cls, zone): + storage_pools = zone.primaryStorages + sp_pools = [] + for storage in storage_pools: + if storage['provider'] and "StorPool" in storage['provider']: + sp_pools.append(storage) + + if len(sp_pools) < 2: + cls.debug("Cannot perform the tests because there aren't the required count of StorPool storage pools %s" % sp_pools) + return + return sp_pools + + @classmethod + def create_snapshot_template(cls, apiclient, services, snapshot_id, zone_id): + cmd = createTemplate.createTemplateCmd() + cmd.displaytext = "TemplateFromSnap" + name = "-".join([cmd.displaytext, random_gen()]) + cmd.name = name + if "ostypeid" in services: + cmd.ostypeid = services["ostypeid"] + elif "ostype" in services: + sub_cmd = listOsTypes.listOsTypesCmd() + sub_cmd.description = services["ostype"] + ostypes = apiclient.listOsTypes(sub_cmd) + + if not isinstance(ostypes, list): + cls.fail("Unable to find Ostype id with desc: %s" % + services["ostype"]) + cmd.ostypeid = ostypes[0].id + else: + cls.fail("Unable to find Ostype is required for creating template") + + cmd.isfeatured = True + cmd.ispublic = True + cmd.isextractable = False + + cmd.snapshotid = snapshot_id + cmd.zoneid = zone_id + apiclient.createTemplate(cmd) + templates = Template.list(apiclient, name=name, templatefilter="self") + if not isinstance(templates, list) and len(templates) < 0: + cls.fail("Unable to find created template with name %s" % name) + template = Template(templates[0].__dict__) + return template + + @classmethod + def verify_snapshot_copies(cls, userapiclient, snapshot_id, zone_ids): + snapshot_entries = Snapshot.list(userapiclient, id=snapshot_id, showunique=False) + if not isinstance(snapshot_entries, list): + cls.fail("Unable to list snapshot for multiple zones") + snapshots = set() + new_list = [] + for obj in snapshot_entries: + if obj.zoneid not in snapshots: + new_list.append(obj) + snapshots.add(obj.zoneid) + + if len(new_list) != len(zone_ids): + cls.fail("Undesired list snapshot size for multiple zones") + for zone_id in zone_ids: + zone_found = False + for entry in new_list: + if entry.zoneid == zone_id: + zone_found = True + break + if zone_found == False: + cls.fail("Unable to find snapshot entry for the zone ID: %s" % zone_id) diff --git a/test/integration/plugins/storpool/storpool.cfg b/test/integration/plugins/storpool/storpool.cfg new file mode 100644 index 000000000000..4eccc9d0f29a --- /dev/null +++ b/test/integration/plugins/storpool/storpool.cfg @@ -0,0 +1,154 @@ +# 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. + +{ + "zones": [ + { + "name": "Zone-A", + "enabled": "True", + "physical_networks": [ + { + "broadcastdomainrange": "Zone", + "name": "physical_network", + "traffictypes": [ + { + "typ": "Guest" + }, + { + "typ": "Management" + } + ], + "providers": [ + { + "broadcastdomainrange": "ZONE", + "name": "VirtualRouter" + }, + { + "broadcastdomainrange": "Pod", + "name": "SecurityGroupProvider" + } + ] + } + ], + "primaryStorages": [ + { + "url": "SP_API_HTTP=1.1.1.1:81;SP_AUTH_TOKEN=11111111;SP_TEMPLATE=ssd", + "name": "ssd", + "provider": "StorPool", + "hypervisor": "KVM", + "path": "/dev/storpool/", + "protocol": "SharedMountPoint", + "capacitybytes": 300000000000, + "tags": "ssd" + }, + { + "url": "SP_API_HTTP=1.1.1.1:81;SP_AUTH_TOKEN=1111111;SP_TEMPLATE=ssd2", + "name": "", + "provider": "StorPool", + "hypervisor": "KVM", + "path": "/dev/storpool/", + "protocol": "SharedMountPoint", + "capacitybytes": 300000000000, + "tags": "" + }, + { + "url": "nfs://1.1.1.1/export/primary", + "name": "primary", + "hypervisor": "KVM", + "tags": "nfs" + }, + { + "url": "rbd://cloudstack:342343223==@1.1.1.1/cloudstack", + "hypervisor": "KVM", + "name": "ceph", + "tags": "ceph" + } + ], + "spEndpoint": [SP_API_HTTP_HOST], + "spEndpointPort": 81, + "spAuthToken": [SP_AUTH_TOKEN] + }, + { + "name": "Zone-B", + "enabled": "True", + "physical_networks": [ + { + "broadcastdomainrange": "Zone", + "name": "physical_network", + "traffictypes": [ + { + "typ": "Guest" + }, + { + "typ": "Management" + } + ], + "providers": [ + { + "broadcastdomainrange": "ZONE", + "name": "VirtualRouter" + }, + { + "broadcastdomainrange": "Pod", + "name": "SecurityGroupProvider" + } + ] + } + ], + "primaryStorages": [ + { + "url": "SP_API_HTTP=1.1.1.1:81;SP_AUTH_TOKEN=111111;SP_TEMPLATE=default", + "name": "", + "provider": "StorPool", + "hypervisor": "KVM", + "path": "/dev/storpool/", + "protocol": "SharedMountPoint", + "capacitybytes": 300000000000, + "tags": "" + }, + { + "url": "nfs://1.1.1.1/export/primary", + "name": "", + "hypervisor": "KVM", + "tags": "" + } + ], + "spEndpoint": [SP_API_HTTP_HOST], + "spEndpointPort": 81, + "spAuthToken": [SP_AUTH_TOKEN] + } + ], + "dbSvr": { + "dbSvr": "127.0.0.1", + "passwd": "cloud", + "db": "cloud", + "port": 3306, + "user": "cloud" + }, + "logger": { + "LogFolderPath": "/tmp/" + }, + "mgtSvr": [ + { + "mgtSvrIp": "", + "port": 8096, + "user": "", + "passwd": "", + "hypervisor": "kvm" + } + ] +} diff --git a/test/integration/plugins/storpool/test_snapshot_copy_on_primary_storage.py b/test/integration/plugins/storpool/test_snapshot_copy_on_primary_storage.py new file mode 100644 index 000000000000..4e627a58f3b7 --- /dev/null +++ b/test/integration/plugins/storpool/test_snapshot_copy_on_primary_storage.py @@ -0,0 +1,255 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import time + +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (createSnapshot, + deleteSnapshot, + copySnapshot, + createVolume, + createTemplate, + listOsTypes) +from marvin.lib.utils import (cleanup_resources, + random_gen) +from marvin.lib.base import (Account, + Zone, + ServiceOffering, + DiskOffering, + VirtualMachine, + Volume, + Snapshot, + Template, + StoragePool) +from marvin.lib.common import (get_domain, + get_zone, + get_template) +from marvin.lib.decoratorGenerators import skipTestIf +from marvin.codes import FAILED, PASS +from nose.plugins.attrib import attr +import logging +from sp_util import (TestData, StorPoolHelper) +import math + +class TestSnapshotCopy(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestSnapshotCopy, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + + cls._cleanup = [] + cls.logger = logging.getLogger('TestSnapshotCopy') + cls.testsNotSupported = False + cls.zones = Zone.list(cls.apiclient) + cls.pools = StoragePool.list(cls.apiclient, status="Up") + enabled_core_zones = [] + if not isinstance(cls.zones, list): + cls.testsNotSupported = True + elif len(cls.zones) < 2: + cls.testsNotSupported = True + else: + for z in cls.zones: + if z.type == 'Core' and z.allocationstate == 'Enabled': + enabled_core_zones.append(z) + if len(enabled_core_zones) < 2: + cls.testsNotSupported = True + + if cls.testsNotSupported == True: + cls.logger.info("Unsupported") + return + + cls.additional_zone = None + for z in enabled_core_zones: + if z.id != cls.zone.id: + cls.additional_zone = z + + cls.storpool_pool = None + for pool in cls.pools: + if pool.provider == "StorPool" and pool.zoneid != cls.zone.id: + cls.storpool_pool = pool + break + + template = get_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"]) + if template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + + # Set Zones and disk offerings + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = template.id + cls.services["iso"]["zoneid"] = cls.zone.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id) + cls._cleanup.append(cls.account) + + cls.helper = StorPoolHelper() + + compute_offering_service = cls.services["service_offerings"]["tiny"].copy() + cls.service_offering = ServiceOffering.create( + cls.apiclient, + compute_offering_service) + cls._cleanup.append(cls.service_offering) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = template.id + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["virtual_machine"], + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls._cleanup.append(cls.virtual_machine) + cls.volume = Volume.list( + cls.apiclient, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + )[0] + + @classmethod + def tearDownClass(cls): + super(TestSnapshotCopy, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.userapiclient = self.testClient.getUserApiClient( + UserName=self.account.name, + DomainName=self.account.domain + ) + self.dbclient = self.testClient.getDbConnection() + self.snapshot_id = None + self.cleanup = [] + + def tearDown(self): + super(TestSnapshotCopy, self).tearDown() + + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_01_take_snapshot_multi_zone(self): + """Test to take volume snapshot in multiple StorPool primary storage pools + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id, zoneids=[str(self.additional_zone.id)], usestoragereplication=True) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_02_copy_snapshot_multi_pools(self): + """Test to take volume snapshot on StorPool primary storage and then copy on StorPool primary storage in another pool + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id) + self.snapshot_id = snapshot.id + Snapshot.copy(self.userapiclient, self.snapshot_id, zone_ids=[str(self.additional_zone.id)], source_zone_id=self.zone.id, usestoragereplication=True) + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_03_take_snapshot_multi_pools_delete_single_zone(self): + """Test to take volume snapshot in multiple StorPool storages in diff zones and delete from one zone + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id, zoneids=[str(self.additional_zone.id)], usestoragereplication=True) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient, self.zone.id) + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.additional_zone.id]) + self.cleanup.append(snapshot) + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_04_copy_snapshot_multi_zone_delete_all(self): + """Test to take volume snapshot on StorPool, copy in another StorPool primary storage in another zone and delete for all + """ + + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id) + self.snapshot_id = snapshot.id + Snapshot.copy(self.userapiclient, self.snapshot_id, zone_ids=[str(self.additional_zone.id)], source_zone_id=self.zone.id, usestoragereplication=True) + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + snapshot_entries = Snapshot.list(self.userapiclient, id=snapshot.id) + if snapshot_entries and isinstance(snapshot_entries, list) and len(snapshot_entries) > 0: + self.fail("Snapshot delete for all zones failed") + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_05_take_snapshot_multi_zone_create_volume_additional_zone(self): + """Test to take volume snapshot on StorPool in multiple zones and create a volume in one of the additional zones + """ + + snapshot = Snapshot.create(self.userapiclient,volume_id=self.volume.id, zoneids=[str(self.additional_zone.id)], usestoragereplication=True) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + disk_offering_id = None + if snapshot.volumetype == 'ROOT': + service = self.services["disk_offering"] + service["disksize"] = math.ceil(snapshot.virtualsize/(1024*1024*1024)) + self.disk_offering = DiskOffering.create( + self.apiclient, + service + ) + self.cleanup.append(self.disk_offering) + disk_offering_id = self.disk_offering.id + + self.volume = Volume.create(self.userapiclient, {"diskname":"StorPoolDisk-1" }, snapshotid=self.snapshot_id, zoneid=self.zone.id, diskofferingid=disk_offering_id) + self.cleanup.append(self.volume) + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + if self.zone.id != self.volume.zoneid: + self.fail("Volume from snapshot not created in the additional zone") + return + + @skipTestIf("testsNotSupported") + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_06_take_snapshot_multi_zone_create_template_additional_zone(self): + """Test to take volume snapshot in multiple StorPool primary storages in diff zones and create a volume in one of the additional zones + """ + snapshot = Snapshot.create(self.userapiclient, volume_id=self.volume.id, zoneids=[str(self.additional_zone.id)], usestoragereplication=True) + self.snapshot_id = snapshot.id + self.helper.verify_snapshot_copies(self.userapiclient, self.snapshot_id, [self.zone.id, self.additional_zone.id]) + self.template = self.helper.create_snapshot_template(self.userapiclient, self.services, self.snapshot_id, self.additional_zone.id) + if self.additional_zone.id != self.template.zoneid: + self.fail("Template from snapshot not created in the additional zone") + time.sleep(420) + Snapshot.delete(snapshot, self.userapiclient) + self.cleanup.append(self.template) + return diff --git a/test/integration/plugins/storpool/test_storpool_tiers.py b/test/integration/plugins/storpool/test_storpool_tiers.py index 71758c24bedc..abea6941fb60 100644 --- a/test/integration/plugins/storpool/test_storpool_tiers.py +++ b/test/integration/plugins/storpool/test_storpool_tiers.py @@ -26,7 +26,9 @@ StoragePool, VirtualMachine, SecurityGroup, - ResourceDetails + ResourceDetails, + Snapshot, + Volume, ) from marvin.lib.common import (get_domain, get_template, @@ -88,17 +90,17 @@ def setUpCloudStack(cls): cls.qos = "SP_QOSCLASS" cls.spTemplate = "SP_TEMPLATE" - cls.disk_offerings_tier1_tags = cls.getDiskOffering(disk_offerings_tier1_tags, cls.qos, "ssd") + cls.disk_offerings_tier1_tags = cls.getDiskOffering(disk_offerings_tier1_tags, cls.qos, cls.testdata[TestData.sp_template_1]["tags"]) - cls.disk_offerings_tier2_tags = cls.getDiskOffering(disk_offerings_tier2_tags, cls.qos, "virtual") + cls.disk_offerings_tier2_tags = cls.getDiskOffering(disk_offerings_tier2_tags, cls.qos, cls.testdata[TestData.sp_template_2]["tags"]) - cls.disk_offerings_tier1_template = cls.getDiskOffering(disk_offerings_tier1_template, cls.spTemplate, "ssd") + cls.disk_offerings_tier1_template = cls.getDiskOffering(disk_offerings_tier1_template, cls.spTemplate, cls.testdata[TestData.sp_template_1]["tags"]) cls.disk_offerings_tier2_template = cls.getDiskOffering(disk_offerings_tier2_template, cls.spTemplate, - "virtual") + cls.testdata[TestData.sp_template_2]["tags"]) cls.disk_offerings_tier2_tags_template = cls.getDiskOffering(disk_offerings_tier2_tags_template, cls.spTemplate, - "virtual") - cls.resourceDetails(cls.qos, cls.disk_offerings_tier2_tags_template.id, "virtual") + cls.testdata[TestData.sp_template_2]["tags"]) + cls.resourceDetails(cls.qos, cls.disk_offerings_tier2_tags_template.id, cls.testdata[TestData.sp_template_1]["tags"]) cls.account = cls.helper.create_account( cls.apiclient, @@ -113,33 +115,35 @@ def setUpCloudStack(cls): cls.helper.set_securityGroups(cls.apiclient, account=cls.account.name, domainid=cls.account.domainid, id=securitygroup.id) - storpool_primary_storage = cls.testdata[TestData.primaryStorage] + sp_pools = cls.helper.get_pool(zone) + assert sp_pools is not None + + storpool_primary_storage = sp_pools[0] storpool_service_offerings = cls.testdata[TestData.serviceOffering] - cls.template_name = storpool_primary_storage.get("name") + cls.template_name = storpool_primary_storage["name"] storage_pool = list_storage_pools( cls.apiclient, name=cls.template_name ) - service_offerings = list_service_offering( - cls.apiclient, - name=cls.template_name - ) - - disk_offerings = list_disk_offering( - cls.apiclient, - name="ssd" - ) - if storage_pool is None: storage_pool = StoragePool.create(cls.apiclient, storpool_primary_storage) else: storage_pool = storage_pool[0] cls.storage_pool = storage_pool + cls.helper.updateStoragePoolTags(cls.apiclient, cls.storage_pool.id, cls.testdata[TestData.sp_template_1]["tags"]) + cls.debug(pprint.pformat(storage_pool)) + + + service_offerings = list_service_offering( + cls.apiclient, + name=cls.template_name + ) + if service_offerings is None: service_offerings = ServiceOffering.create(cls.apiclient, storpool_service_offerings) else: @@ -167,6 +171,29 @@ def setUpCloudStack(cls): cls.random_data_0 = random_gen(size=100) cls.test_dir = "/tmp" cls.random_data = "random.data" + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=cls.zone.id, + templateid=cls.template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + overridediskofferingid=cls.disk_offerings_tier1_tags.id, + hypervisor=cls.hypervisor, + rootdisksize=10 + ) + + volume = list_volumes( + cls.apiclient, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + )[0] + cls.snapshot = Snapshot.create( + cls.apiclient, + volume.id, + ) return @classmethod @@ -200,6 +227,7 @@ def tearDown(self): def test_01_check_tags_on_deployed_vm_and_datadisk(self): virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_02_change_offering_on_attached_root_disk(self): @@ -213,6 +241,7 @@ def test_02_change_offering_on_attached_root_disk(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) def test_03_change_offering_on_attached_data_disk(self): virtual_machine_tier1_tag = self.deploy_vm_and_check_tier_tag() @@ -225,6 +254,7 @@ def test_03_change_offering_on_attached_data_disk(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_04_check_templates_on_deployed_vm_and_datadisk(self): @@ -246,6 +276,7 @@ def test_04_check_templates_on_deployed_vm_and_datadisk(self): for v in volumes: self.check_storpool_template(v, self.disk_offerings_tier1_template.id, self.spTemplate) virtual_machine_template_tier1.stop(self.apiclient, forced=True) + virtual_machine_template_tier1.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_05_check_templates_on_deployed_vm_and_datadisk_tier2(self): @@ -267,6 +298,7 @@ def test_05_check_templates_on_deployed_vm_and_datadisk_tier2(self): for v in volumes: self.check_storpool_template(v, self.disk_offerings_tier2_template.id, self.spTemplate) virtual_machine_template_tier2.stop(self.apiclient, forced=True) + virtual_machine_template_tier2.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_06_change_offerings_with_tags_detached_volume(self): @@ -300,6 +332,7 @@ def test_06_change_offerings_with_tags_detached_volume(self): self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_tags.id, volumes[0].size) self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + virtual_machine_tier2_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_07_change_offerings_with_template_detached_volume(self): @@ -332,6 +365,7 @@ def test_07_change_offerings_with_template_detached_volume(self): self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_template.id, volumes[0].size) self.check_storpool_template(volume=volumes[0], disk_offering_id=self.disk_offerings_tier1_template.id, qos_or_template=self.spTemplate) + virtual_machine_tier2_template.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_08_deploy_vm_with_tags_and_template_in_offerings(self): @@ -370,6 +404,7 @@ def test_08_deploy_vm_with_tags_and_template_in_offerings(self): self.changeOfferingForVolume(volumes[0].id, self.disk_offerings_tier1_tags.id, volumes[0].size) self.vc_policy_tags(volumes=volumes, vm=virtual_machine_tier2_template, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + virtual_machine_tier2_template.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_09_resize_root_volume(self): @@ -386,6 +421,7 @@ def test_09_resize_root_volume(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_10_shrink_root_volume(self): @@ -403,6 +439,7 @@ def test_10_shrink_root_volume(self): listall=True) self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_11_resize_data_volume(self): @@ -419,6 +456,7 @@ def test_11_resize_data_volume(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") def test_12_shrink_data_volume(self): @@ -436,6 +474,25 @@ def test_12_shrink_data_volume(self): self.vc_policy_tags(volumes=root_volume, vm=virtual_machine_tier1_tag, qos_or_template=self.qos, disk_offering_id=self.disk_offerings_tier2_tags.id, attached=True) virtual_machine_tier1_tag.stop(self.apiclient, forced=True) + virtual_machine_tier1_tag.delete(self.apiclient, expunge=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_13_deploy_vm_from_volume_check_tags(self): + vm = self.deploy_vm_from_snapshot_or_template(snapshotid=self.snapshot.id, is_snapshot=False) + root_volume = list_volumes(self.apiclient, virtualmachineid=vm.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=vm, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + vm.delete(self.apiclient, expunge=True) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") + def test_14_deploy_vm_from_snapshot_check_tags(self): + vm = self.deploy_vm_from_snapshot_or_template(snapshotid=self.snapshot.id, is_snapshot=True) + root_volume = list_volumes(self.apiclient, virtualmachineid=vm.id, type="ROOT", + listall=True) + self.vc_policy_tags(volumes=root_volume, vm=vm, qos_or_template=self.qos, + disk_offering_id=self.disk_offerings_tier1_tags.id, attached=True) + vm.delete(self.apiclient, expunge=True) def deploy_vm_and_check_tier_tag(self): virtual_machine_tier1_tag = VirtualMachine.create( @@ -542,3 +599,45 @@ def changeOfferingForVolume(self, volume_id, disk_offering_id, size, shrinkok=No change_offering_for_volume_cmd.shrinkok = shrinkok return self.apiclient.changeOfferingForVolume(change_offering_for_volume_cmd) + + def deploy_vm_from_snapshot_or_template(self, snapshotid, is_snapshot=False): + if is_snapshot: + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + snapshotid=snapshotid, + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + return virtual_machine + volume = Volume.create_from_snapshot( + self.apiclient, + snapshot_id=snapshotid, + services=self.services, + account=self.account.name, + domainid=self.account.domainid, + disk_offering=self.disk_offerings_tier1_tags.id, + zoneid=self.zone.id, + size=10 + ) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "StorPool-%s" % uuid.uuid4()}, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + serviceofferingid=self.service_offering.id, + volumeid=volume.id, + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + return virtual_machine diff --git a/test/integration/smoke/test_accounts.py b/test/integration/smoke/test_accounts.py index 209c8431b236..d30f9f398902 100644 --- a/test/integration/smoke/test_accounts.py +++ b/test/integration/smoke/test_accounts.py @@ -1744,7 +1744,7 @@ def test_user_cannot_renew_other_keys(self): self.apiclient, id=self.account_2.id )[0].user - with self.assertRaises(CloudstackAPIException) as e: + with self.assertRaises(Exception) as e: User.registerUserKeys(cs_api, users[0].id) diff --git a/test/integration/smoke/test_attach_multiple_volumes.py b/test/integration/smoke/test_attach_multiple_volumes.py index 81199bcfdfa1..9357e4d65090 100644 --- a/test/integration/smoke/test_attach_multiple_volumes.py +++ b/test/integration/smoke/test_attach_multiple_volumes.py @@ -285,6 +285,8 @@ def test_attach_multiple_volumes(self): self.query_async_job(vol3_jobId.jobid) self.query_async_job(vol4_jobId.jobid) + time.sleep(60) + # List all the volumes attached to the instance. Includes even the Root disk. list_volume_response = Volume.list( self.apiClient, @@ -337,6 +339,8 @@ def test_attach_and_distribute_multiple_volumes(self): self.query_async_job(vol5_jobId.jobid) self.query_async_job(vol6_jobId.jobid) + time.sleep(60) + volumes = Volume.list(self.apiClient, virtualmachineid=self.virtual_machine.id, type="DATADISK", diff --git a/test/integration/smoke/test_backup_recovery_dummy.py b/test/integration/smoke/test_backup_recovery_dummy.py index b4789bd0f249..3e2f16f8c686 100644 --- a/test/integration/smoke/test_backup_recovery_dummy.py +++ b/test/integration/smoke/test_backup_recovery_dummy.py @@ -18,10 +18,11 @@ from marvin.cloudstackTestCase import cloudstackTestCase from marvin.lib.utils import (cleanup_resources) -from marvin.lib.base import (Account, ServiceOffering, VirtualMachine, BackupOffering, Configurations, Backup) +from marvin.lib.base import (Account, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, Configurations, Backup, Volume) from marvin.lib.common import (get_domain, get_zone, get_template) from nose.plugins.attrib import attr from marvin.codes import FAILED +import time class TestDummyBackupAndRecovery(cloudstackTestCase): @@ -44,33 +45,34 @@ def setUpClass(cls): cls._cleanup = [] # Check backup configuration values, set them to enable the dummy provider - backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled', zoneid=cls.zone.id) - backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin', zoneid=cls.zone.id) + backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled') + backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin') cls.backup_enabled = backup_enabled_cfg[0].value cls.backup_provider = backup_provider_cfg[0].value if cls.backup_enabled == "false": - Configurations.update(cls.api_client, 'backup.framework.enabled', value='true', zoneid=cls.zone.id) + cls.skipTest(cls, reason="Test can be run only if the config backup.framework.enabled is true") if cls.backup_provider != "dummy": - Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value='dummy', zoneid=cls.zone.id) + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value='dummy') if cls.hypervisor.lower() != 'simulator': return cls.account = Account.create(cls.api_client, cls.services["account"], domainid=cls.domain.id) cls.offering = ServiceOffering.create(cls.api_client,cls.services["service_offerings"]["small"]) + cls.diskoffering = DiskOffering.create(cls.api_client, cls.services["disk_offering"]) cls.vm = VirtualMachine.create(cls.api_client, cls.services["small"], accountid=cls.account.name, domainid=cls.account.domainid, serviceofferingid=cls.offering.id, - mode=cls.services["mode"]) - cls._cleanup = [cls.offering, cls.account] + diskofferingid=cls.diskoffering.id, mode=cls.services["mode"]) + cls._cleanup = [cls.offering, cls.diskoffering, cls.account] # Import a dummy backup offering to use on tests cls.provider_offerings = BackupOffering.listExternal(cls.api_client, cls.zone.id) cls.debug("Importing backup offering %s - %s" % (cls.provider_offerings[0].externalid, cls.provider_offerings[0].name)) - cls.offering = BackupOffering.importExisting(cls.api_client, cls.zone.id, cls.provider_offerings[0].externalid, + cls.backup_offering = BackupOffering.importExisting(cls.api_client, cls.zone.id, cls.provider_offerings[0].externalid, cls.provider_offerings[0].name, cls.provider_offerings[0].description) - cls._cleanup.append(cls.offering) + cls._cleanup.append(cls.backup_offering) @classmethod def tearDownClass(cls): @@ -79,10 +81,8 @@ def tearDownClass(cls): cleanup_resources(cls.api_client, cls._cleanup) # Restore original backup framework values values - if cls.backup_enabled == "false": - Configurations.update(cls.api_client, 'backup.framework.enabled', value=cls.backup_enabled, zoneid=cls.zone.id) if cls.backup_provider != "dummy": - Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value=cls.backup_provider, zoneid=cls.zone.id) + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value=cls.backup_provider) except Exception as e: raise Exception("Warning: Exception during cleanup : %s" % e) @@ -139,7 +139,7 @@ def test_vm_backup_lifecycle(self): self.assertEqual(backups, None, "There should not exist any backup for the VM") # Assign VM to offering and create ad-hoc backup - self.offering.assignOffering(self.apiclient, self.vm.id) + self.backup_offering.assignOffering(self.apiclient, self.vm.id) Backup.create(self.apiclient, self.vm.id) # Verify backup is created for the VM @@ -155,4 +155,68 @@ def test_vm_backup_lifecycle(self): self.assertEqual(backups, None, "There should not exist any backup for the VM") # Remove VM from offering - self.offering.removeOffering(self.apiclient, self.vm.id) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="false") + def test_vm_backup_create_vm_from_backup(self): + """ + Test creating a new VM from a backup + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + + Backup.create(self.apiclient, self.vm.id, "backup1") + Backup.create(self.apiclient, self.vm.id, "backup2") + + # Verify backup is created for the VM + backups = Backup.list(self.apiclient, self.vm.id) + #self.cleanup.extend(backups) + #self.cleanup.append(backups[0]) + self.assertEqual(len(backups), 2, "There should exist two backups for the VM") + + # Remove VM from offering + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + # Verify no. of backups after removing the backup offering + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 2, "There should exist two backups for the VM") + + # Create a new VM from first backup + new_vm_name = "vm-from-backup1-" + str(int(time.time())) + new_vm = Backup.createVMFromBackup( + self.apiclient, + self.services["small"], + mode=self.services["mode"], + backupid=backups[0].id, + vmname=new_vm_name, + accountname=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id + ) + self.cleanup.append(new_vm) + + # Verify the new VM was created successfully + self.assertIsNotNone(new_vm, "Failed to create VM from backup") + self.assertEqual(new_vm.name, new_vm_name, "VM name does not match the requested name") + + # Verify the new VM is running + self.assertEqual(new_vm.state, "Running", "New VM should be in Running state") + + # Verify the new VM has the correct service offering + self.assertEqual(new_vm.serviceofferingid, self.offering.id, + "New VM should have the correct service offering") + + # Verify the new VM has the correct zone + self.assertEqual(new_vm.zoneid, self.zone.id, "New VM should be in the correct zone") + + # Verify the new VM has the correct number of volumes (ROOT + DATADISK) + volumes = Volume.list( + self.apiclient, + virtualmachineid=new_vm.id, + listall=True + ) + self.assertTrue(isinstance(volumes, list), "List volumes should return a valid list") + self.assertEqual(2, len(volumes), "The new VM should have 2 volumes (ROOT + DATADISK)") + + # Delete backups + Backup.delete(self.apiclient, backups[0].id) + Backup.delete(self.apiclient, backups[1].id) diff --git a/test/integration/smoke/test_backup_recovery_kboss.py b/test/integration/smoke/test_backup_recovery_kboss.py new file mode 100644 index 000000000000..10d823da195c --- /dev/null +++ b/test/integration/smoke/test_backup_recovery_kboss.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# 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. + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib import utils +from marvin.lib.base import (Account, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, + Backup, Configurations, Volume, StoragePool) +from marvin.lib.common import (get_domain, get_zone, get_template) +from nose.plugins.attrib import attr +from marvin.codes import FAILED +import time +import tempfile +import urllib.parse +import urllib.request +import os + +class TestKBOSSBackupAndRecovery(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + # Setup + + cls.testClient = super(TestKBOSSBackupAndRecovery, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + print(cls.api_client) + cls.services = cls.testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services["mode"] = cls.zone.networktype + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.domain = get_domain(cls.api_client) + cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + if cls.template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = cls.template.id + cls._cleanup = [] + + if cls.hypervisor.lower() != 'kvm': + cls.skipTest(cls, reason="Test can be run only on KVM hypervisor") + + cls.storage_pool = StoragePool.list(cls.api_client)[0] + if cls.storage_pool.type.lower() != 'networkfilesystem': + cls.skipTest(cls, reason="Test can be run only if the primary storage is of type NFS") + + # Check backup configuration values, set them to enable the kboss provider + backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled') + backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin') + cls.backup_enabled = backup_enabled_cfg[0].value + cls.backup_provider = backup_provider_cfg[0].value + + if cls.backup_enabled == "false": + cls.skipTest(cls, reason="Test can be run only if the config backup.framework.enabled is true") + if cls.backup_provider != "kboss": + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value='kboss') + + cls.account = Account.create(cls.api_client, cls.services["account"], domainid=cls.domain.id) + + cls._cleanup = [cls.account] + + cls.basic_backup_offering = BackupOffering.createOffering(cls.api_client, utils.random_gen(), utils.random_gen(), cls.zone.id) + cls._cleanup.append(cls.basic_backup_offering) + cls.compress_backup_offering = BackupOffering.createOffering(cls.api_client, utils.random_gen(), utils.random_gen(), cls.zone.id, compress=True) + cls._cleanup.append(cls.compress_backup_offering) + cls.validate_backup_offering = BackupOffering.createOffering(cls.api_client, utils.random_gen(), utils.random_gen(), cls.zone.id, validate=True, + validationsteps="screenshot") + cls._cleanup.append(cls.validate_backup_offering) + + cls.offering = ServiceOffering.create(cls.api_client,cls.services["service_offerings"]["small"]) + cls.diskoffering = DiskOffering.create(cls.api_client, cls.services["disk_offering"]) + cls._cleanup.extend([cls.offering, cls.diskoffering]) + cls.vm = VirtualMachine.create(cls.api_client, cls.services["small"], accountid=cls.account.name, + domainid=cls.account.domainid, serviceofferingid=cls.offering.id, + diskofferingid=cls.diskoffering.id, mode=cls.services["mode"]) + + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + utils.cleanup_resources(cls.api_client, cls._cleanup) + + if cls.backup_provider != "kboss": + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value=cls.backup_provider) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def setUp(self): + if self.hypervisor.lower() != 'kvm': + raise self.skipTest("Skipping test cases which must only run for Simulator") + self.cleanup = [] + + def tearDown(self): + try: + utils.cleanup_resources(self.api_client, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def waitForCompression(self, vm): + def checkBackupCompression(): + backups = Backup.list(self.api_client, vm.id) + if isinstance(backups, list) and len(backups) != 0 and backups[0].compressionstatus == "Compressed": + return True, None + return False, None + + res, _ = utils.wait_until(10, 60, checkBackupCompression) + if not res: + self.fail("Failed to wait for backup compression of VM %s" % vm.id) + + def waitForValidation(self, vm): + def checkBackupValidation(): + backups = Backup.list(self.api_client, vm.id) + if isinstance(backups, list) and len(backups) != 0 and backups[0].validationstatus == "Valid": + return True, None + return False, None + + res, _ = utils.wait_until(10, 60, checkBackupValidation) + if not res: + self.fail("Failed to wait for backup compression of VM %s" % vm.id) + + def download_screenshot(self, backupid): + extract_ss = Backup.downloadValidationScreenshot(self.api_client, backupid) + + try: + formatted_url = urllib.parse.unquote_plus(extract_ss.url) + self.debug("Attempting to download screenshot at url %s" % formatted_url) + response = urllib.request.urlopen(formatted_url) + self.debug("response from screenshot url %s" % response.getcode()) + fd, path = tempfile.mkstemp() + self.debug("Saving screenshot %s to path %s" % (backupid, path)) + os.close(fd) + with open(path, 'wb') as fd: + fd.write(response.read()) + self.debug("Saved screenshot successfully") + except Exception: + self.fail( + "Extract screenshot of backup Failed with invalid URL %s (backup id: %s)" \ + % (extract_ss.url, backupid) + ) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_lifecycle(self): + """ + Test VM backup lifecycle + """ + + # Verify there are no backups for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Assign VM to offering and create ad-hoc backup + self.basic_backup_offering.assignOffering(self.api_client, self.vm.id) + Backup.create(self.api_client, self.vm.id) + + # Verify backup is created for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(len(backups), 1, "There should exist only one backup for the VM") + backup = backups[0] + + # Delete backup + Backup.delete(self.api_client, backup.id) + + # Verify backup is deleted + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Remove VM from offering + self.basic_backup_offering.removeOffering(self.api_client, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_lifecycle_with_compression(self): + """ + Test VM backup lifecycle with compression + """ + + compression_enabled = Configurations.list(self.api_client, name='backup.compression.task.enabled') + if compression_enabled[0].value == "false": + self.skipTest("Skipping test due to backup compression task is disabled.") + + # Verify there are no backups for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Assign VM to offering and create ad-hoc backup + self.compress_backup_offering.assignOffering(self.api_client, self.vm.id) + Backup.create(self.api_client, self.vm.id) + + # Verify backup is created for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(len(backups), 1, "There should exist only one backup for the VM") + backup = backups[0] + + self.waitForCompression(self.vm) + + # Delete backup + Backup.delete(self.api_client, backup.id) + + # Verify backup is deleted + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Remove VM from offering + self.compress_backup_offering.removeOffering(self.api_client, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_lifecycle_with_validation(self): + """ + Test VM backup lifecycle with validation + """ + + validation_enabled = Configurations.list(self.api_client, name='backup.validation.task.enabled') + if validation_enabled[0].value == "false": + self.skipTest("Skipping test due to backup compression task is disabled.") + + # Verify there are no backups for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Assign VM to offering and create ad-hoc backup + self.validate_backup_offering.assignOffering(self.api_client, self.vm.id) + Backup.create(self.api_client, self.vm.id) + + # Verify backup is created for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(len(backups), 1, "There should exist only one backup for the VM") + backup = backups[0] + + # Verify validation is performed + self.waitForValidation(self.vm) + self.download_screenshot(backup.id) + + # Delete backup + Backup.delete(self.api_client, backup.id) + + # Verify backup is deleted + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Remove VM from offering + self.validate_backup_offering.removeOffering(self.api_client, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_create_vm_from_backup(self): + """ + Test creating a new VM from a backup + """ + self.basic_backup_offering.assignOffering(self.api_client, self.vm.id) + + # Create a file and take backup + try: + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("touch test_backup_and_recovery.txt") + except Exception as err: + self.fail("SSH failed for Virtual machine: %s due to %s" % (self.vm.ipaddress, err)) + + time.sleep(5) + + Backup.create(self.api_client, self.vm.id, "backup1") + Backup.create(self.api_client, self.vm.id, "backup2") + + # Verify backup is created for the VM + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(len(backups), 2, "There should exist two backups for the VM") + + # Remove VM from offering + self.basic_backup_offering.removeOffering(self.api_client, self.vm.id) + + # Verify no. of backups after removing the backup offering + backups = Backup.list(self.api_client, self.vm.id) + self.assertEqual(len(backups), 2, "There should exist two backups for the VM") + + # Create a new VM from first backup + new_vm_name = "vm-from-backup1-" + str(int(time.time())) + new_vm = Backup.createVMFromBackup( + self.api_client, + self.services["small"], + mode=self.services["mode"], + backupid=backups[0].id, + vmname=new_vm_name, + accountname=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + networkids=None, + templateid=None + ) + self.cleanup.append(new_vm) + + # Verify the new VM was created successfully + self.assertIsNotNone(new_vm, "Failed to create VM from backup") + self.assertEqual(new_vm.name, new_vm_name, "VM name does not match the requested name") + + # Verify the new VM is running + self.assertEqual(new_vm.state, "Running", "New VM should be in Running state") + + # Verify the new VM has the correct service offering + self.assertEqual(new_vm.serviceofferingid, self.offering.id, + "New VM should have the correct service offering") + + # Verify the new VM has the correct zone + self.assertEqual(new_vm.zoneid, self.zone.id, "New VM should be in the correct zone") + + # Verify the new VM has the correct number of volumes (ROOT + DATADISK) + volumes = Volume.list( + self.api_client, + virtualmachineid=new_vm.id, + listall=True + ) + self.assertTrue(isinstance(volumes, list), "List volumes should return a valid list") + self.assertEqual(2, len(volumes), "The new VM should have 2 volumes (ROOT + DATADISK)") + + # Verify that the file is present in the Instance created from backup + try: + ssh_client_new_vm = new_vm.get_ssh_client(reconnect=True) + result = ssh_client_new_vm.execute("ls test_backup_and_recovery.txt") + self.assertEqual(result[0], "test_backup_and_recovery.txt", + "Instance created from Backup should have the same file as the backup.") + except Exception as err: + self.fail("SSH failed for Virtual machine: %s due to %s" % (self.vm.ipaddress, err)) + + # Delete backups + Backup.delete(self.api_client, backups[0].id) + Backup.delete(self.api_client, backups[1].id) diff --git a/test/integration/smoke/test_backup_recovery_nas.py b/test/integration/smoke/test_backup_recovery_nas.py new file mode 100644 index 000000000000..41aa82bd0857 --- /dev/null +++ b/test/integration/smoke/test_backup_recovery_nas.py @@ -0,0 +1,596 @@ +#!/usr/bin/env python +# 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. + +from marvin.cloudstackAPI import listZones +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import (cleanup_resources) +from marvin.lib.base import (Account, Network, ServiceOffering, DiskOffering, VirtualMachine, BackupOffering, + NetworkOffering, BackupRepository, Backup, Configurations, Volume, StoragePool) +from marvin.lib.common import (get_domain, get_zone, get_template) +from nose.plugins.attrib import attr +from marvin.codes import FAILED +import time + +class TestNASBackupAndRecovery(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + # Setup + + cls.testClient = super(TestNASBackupAndRecovery, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.services["mode"] = cls.zone.networktype + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.domain = get_domain(cls.api_client) + cls.template = get_template(cls.api_client, cls.zone.id, cls.services["ostype"]) + if cls.template == FAILED: + assert False, "get_template() failed to return template with description %s" % cls.services["ostype"] + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = cls.template.id + cls._cleanup = [] + + if cls.hypervisor.lower() != 'kvm': + cls.skipTest(cls, reason="Test can be run only on KVM hypervisor") + + cls.storage_pool = StoragePool.list(cls.api_client)[0] + if cls.storage_pool.type.lower() != 'networkfilesystem': + cls.skipTest(cls, reason="Test can be run only if the primary storage is of type NFS") + + # Check backup configuration values, set them to enable the nas provider + backup_enabled_cfg = Configurations.list(cls.api_client, name='backup.framework.enabled') + backup_provider_cfg = Configurations.list(cls.api_client, name='backup.framework.provider.plugin') + incremental_backup_enabled_cfg = Configurations.list(cls.api_client, name='nas.backup.incremental.enabled') + cls.backup_enabled = backup_enabled_cfg[0].value + cls.backup_provider = backup_provider_cfg[0].value + cls.incremental_backup_enabled = incremental_backup_enabled_cfg[0].value + + if cls.backup_enabled == "false": + cls.skipTest(cls, reason="Test can be run only if the config backup.framework.enabled is true") + if cls.backup_provider != "nas": + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value='nas') + if cls.incremental_backup_enabled == "false": + Configurations.update(cls.api_client, 'nas.backup.incremental.enabled', value='true') + + cls.account = Account.create(cls.api_client, cls.services["account"], domainid=cls.domain.id) + + cls._cleanup = [cls.account] + + # Create NAS backup repository and offering. Use the same directory as the storage pool + cls.backup_repository = BackupRepository.add(cls.api_client, zoneid=cls.zone.id, name="Nas", + address=cls.storage_pool.ipaddress + ":" + cls.storage_pool.path, + provider="nas", type="nfs",) + cls._cleanup.append(cls.backup_repository) + cls.provider_offerings = BackupOffering.listExternal(cls.api_client, cls.zone.id) + cls.backup_offering = BackupOffering.importExisting(cls.api_client, cls.zone.id, cls.provider_offerings[0].externalid, + cls.provider_offerings[0].name, cls.provider_offerings[0].description) + cls._cleanup.append(cls.backup_offering) + + cls.offering = ServiceOffering.create(cls.api_client,cls.services["service_offerings"]["small"]) + cls.diskoffering = DiskOffering.create(cls.api_client, cls.services["disk_offering"]) + cls._cleanup.extend([cls.offering, cls.diskoffering, cls.account]) + cls.vm = VirtualMachine.create(cls.api_client, cls.services["small"], accountid=cls.account.name, + domainid=cls.account.domainid, serviceofferingid=cls.offering.id, + diskofferingid=cls.diskoffering.id, mode=cls.services["mode"]) + + + @classmethod + def tearDownClass(cls): + try: + # Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + + if cls.backup_provider != "nas": + Configurations.update(cls.api_client, 'backup.framework.provider.plugin', value=cls.backup_provider) + if cls.incremental_backup_enabled == "false": + Configurations.update(cls.api_client, 'nas.backup.incremental.enabled', value="false") + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + if self.hypervisor.lower() != 'kvm': + raise self.skipTest("Skipping test cases which must only run for Simulator") + self.cleanup = [] + + def tearDown(self): + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def vm_backup_create_vm_from_backup_int(self, templateid=None, networkids=None): + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + + # Create a file and take backup + try: + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("touch test_backup_and_recovery.txt") + except Exception as err: + self.fail("SSH failed for Virtual machine: %s due to %s" % (self.vm.ipaddress, err)) + + time.sleep(5) + + Backup.create(self.apiclient, self.vm.id, "backup1") + Backup.create(self.apiclient, self.vm.id, "backup2") + + # Verify backup is created for the VM + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 2, "There should exist two backups for the VM") + + # Remove VM from offering + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + # Verify no. of backups after removing the backup offering + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 2, "There should exist two backups for the VM") + + # Create a new VM from first backup + new_vm_name = "vm-from-backup1-" + str(int(time.time())) + new_vm = Backup.createVMFromBackup( + self.apiclient, + self.services["small"], + mode=self.services["mode"], + backupid=backups[0].id, + vmname=new_vm_name, + accountname=self.account.name, + domainid=self.account.domainid, + zoneid=self.destZone.id, + networkids=networkids, + templateid=templateid + ) + self.cleanup.append(new_vm) + + # Verify the new VM was created successfully + self.assertIsNotNone(new_vm, "Failed to create VM from backup") + self.assertEqual(new_vm.name, new_vm_name, "VM name does not match the requested name") + + # Verify the new VM is running + self.assertEqual(new_vm.state, "Running", "New VM should be in Running state") + + # Verify the new VM has the correct service offering + self.assertEqual(new_vm.serviceofferingid, self.offering.id, + "New VM should have the correct service offering") + + # Verify the new VM has the correct zone + self.assertEqual(new_vm.zoneid, self.destZone.id, "New VM should be in the correct zone") + + # Verify the new VM has the correct number of volumes (ROOT + DATADISK) + volumes = Volume.list( + self.apiclient, + virtualmachineid=new_vm.id, + listall=True + ) + self.assertTrue(isinstance(volumes, list), "List volumes should return a valid list") + self.assertEqual(2, len(volumes), "The new VM should have 2 volumes (ROOT + DATADISK)") + + # Verify that the file is present in the Instance created from backup + try: + ssh_client_new_vm = new_vm.get_ssh_client(reconnect=True) + result = ssh_client_new_vm.execute("ls test_backup_and_recovery.txt") + self.assertEqual(result[0], "test_backup_and_recovery.txt", + "Instance created from Backup should have the same file as the backup.") + except Exception as err: + self.fail("SSH failed for Virtual machine: %s due to %s" % (self.vm.ipaddress, err)) + + # Delete backups + Backup.delete(self.apiclient, backups[0].id) + Backup.delete(self.apiclient, backups[1].id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_lifecycle(self): + """ + Test VM backup lifecycle + """ + + # Verify there are no backups for the VM + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Assign VM to offering and create ad-hoc backup + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + Backup.create(self.apiclient, self.vm.id) + + # Verify backup is created for the VM + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 1, "There should exist only one backup for the VM") + backup = backups[0] + + # Delete backup + Backup.delete(self.apiclient, backup.id) + + # Verify backup is deleted + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(backups, None, "There should not exist any backup for the VM") + + # Remove VM from offering + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_create_vm_from_backup(self): + """ + Test creating a new VM from a backup + """ + self.destZone = self.zone + self.vm_backup_create_vm_from_backup_int() + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_vm_backup_create_vm_from_backup_in_another_zone(self): + """ + Test creating a new VM from a backup in another zone + """ + cmd = listZones.listZonesCmd() + zones = self.apiclient.listZones(cmd) + if not isinstance(zones, list): + raise Exception("Failed to find zones.") + if len(zones) < 2: + self.skipTest("Skipping test due to there are less than two zones.") + return + self.destZone = zones[1] + + template = get_template(self.api_client, self.destZone.id, self.services["ostype"]) + + list_isolated_network_offerings_response = NetworkOffering.list( + self.apiclient, + name="DefaultIsolatedNetworkOfferingWithSourceNatService" + ) + isolated_network_offering_id = list_isolated_network_offerings_response[0].id + network = { + "name": "Network-", + "displaytext": "Network-" + } + network["name"] = self.account.name + " -destZone" + network["displaytext"] = self.account.name + " -destZone" + network = Network.create( + self.apiclient, + network, + accountid=self.account.name, + domainid=self.domain.id, + networkofferingid=isolated_network_offering_id, + zoneid=self.destZone.id + ) + + backup_repository = self.backup_repository.update(self.api_client, crosszoneinstancecreation=True) + self.assertEqual(backup_repository.crosszoneinstancecreation, True, "Cross-Zone Instance Creation could not be enabled on the backup repository") + + self.vm_backup_create_vm_from_backup_int(template.id, [network.id]) + + # ------------------------------------------------------------------ + # Incremental backup tests (RFC #12899 / PR #13074) + # ------------------------------------------------------------------ + # These tests exercise the incremental NAS backup chain semantics: + # full -> incN cadence, restore-from-incremental, delete-middle chain + # repair, refuse-delete-full-with-children, and stopped-VM fallback. + # + # All tests set nas.backup.full.every to a small value (3) so a chain + # forms quickly without needing many backup iterations. The original + # zone value (whatever the test environment has configured) is captured + # before the test runs and restored verbatim in finally, so we don't + # leak config changes across tests on shared environments. + + def _set_full_every(self, value): + Configurations.update(self.apiclient, name='nas.backup.full.every', + value=str(value), zoneid=self.zone.id) + + def _get_full_every(self): + """Read the current zone-scoped (or global fallback) value of nas.backup.full.every.""" + configs = Configurations.list(self.apiclient, name='nas.backup.full.every', + zoneid=self.zone.id) + if configs and len(configs) > 0 and configs[0].value is not None: + return configs[0].value + # Fall back to global default — Configurations.list returns the global value + # when no zone override exists. Defensive fallback to '10' (the framework default). + return '10' + + def _backup_type(self, backup): + # Backup objects expose `type`; for chained backups it's "INCREMENTAL", else "FULL". + return getattr(backup, 'type', 'FULL') or 'FULL' + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_incremental_chain_cadence(self): + """ + With nas.backup.full.every=3, the sequence of backups should be + FULL, INCREMENTAL, INCREMENTAL, FULL, INCREMENTAL, ... + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + original_full_every = self._get_full_every() + self._set_full_every(3) + try: + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("touch /root/incremental_marker_1.txt") + + created = [] + for i in range(5): + Backup.create(self.apiclient, self.vm.id, "inc_chain_%d" % i) + # write a small change so each incremental has something to capture + ssh_client_vm.execute("dd if=/dev/urandom of=/root/delta_%d bs=64k count=4 2>/dev/null" % i) + time.sleep(2) + created = Backup.list(self.apiclient, self.vm.id) + + self.assertEqual(len(created), 5, "Expected 5 backups after 5 Backup.create calls") + # Sort oldest-first by date + created.sort(key=lambda b: b.created) + + expected = ['FULL', 'INCREMENTAL', 'INCREMENTAL', 'FULL', 'INCREMENTAL'] + actual = [self._backup_type(b).upper() for b in created] + self.assertEqual(actual, expected, + "With nas.backup.full.every=3, chain pattern should be %s but was %s" % (expected, actual)) + + # Cleanup all backups (newest first to satisfy chain rules without forced=true) + for b in reversed(created): + Backup.delete(self.apiclient, b.id) + finally: + self._set_full_every(original_full_every) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_incremental_after_vm_restart(self): + """ + Regression for the parent-checkpoint recreation bug (PR #13074): an incremental + backup taken AFTER the VM has been restarted must still succeed and restore + correctly. + + A VM (re)start rebuilds the libvirt domain XML and wipes libvirt's checkpoint + registry, while the dirty bitmap persists on the qcow2. The agent must then + re-register the parent checkpoint with `checkpoint-create --redefine` (from the + saved checkpoint XML) rather than a fresh create — a fresh create fails with + "Bitmap already exists", and qemu-img cannot drop the bitmap on a running disk. + + How this was reproduced manually on a libvirt 10.0.0 host, and what this test + automates: + FULL + marker1 -> stop/start the VM (wipes the checkpoint registry) + -> INCREMENTAL + marker2 -> restore the tip -> both markers present. + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + original_full_every = self._get_full_every() + # High cadence so the post-restart backup is INCREMENTAL, not a periodic FULL. + self._set_full_every(100) + backups = [] + try: + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("echo restart-test-1 > /root/restart_marker_1.txt; sync") + + # 1) FULL anchor + Backup.create(self.apiclient, self.vm.id, "restart_full") + time.sleep(2) + + # 2) Restart the VM — wipes libvirt's checkpoint registry (the bug trigger). + self.vm.stop(self.apiclient) + self.vm.start(self.apiclient) + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("echo restart-test-2 > /root/restart_marker_2.txt; sync") + + # 3) INCREMENTAL after the restart — the previously-broken path. + Backup.create(self.apiclient, self.vm.id, "restart_incr") + time.sleep(2) + + backups = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(backups), 2, + "Expected FULL + INCREMENTAL after restart, got %d" % len(backups)) + backups.sort(key=lambda b: b.created) + self.assertEqual(self._backup_type(backups[0]).upper(), 'FULL', + "First backup should be FULL") + self.assertEqual(self._backup_type(backups[1]).upper(), 'INCREMENTAL', + "Backup taken after the VM restart must be INCREMENTAL, not silently a FULL") + + # 4) Restore the tip (incremental) and verify BOTH markers survived the chain + # across the restart — i.e. the post-restart incremental really captured data. + new_vm_name = "vm-restart-restore-" + str(int(time.time())) + new_vm = Backup.createVMFromBackup( + self.apiclient, + self.services["small"], + mode=self.services["mode"], + backupid=backups[1].id, + vmname=new_vm_name, + accountname=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id + ) + self.cleanup.append(new_vm) + self.assertIsNotNone(new_vm, "Failed to create VM from the post-restart incremental backup") + self.assertEqual(new_vm.state, "Running", "Restored VM should be Running") + + ssh_new = new_vm.get_ssh_client(reconnect=True) + r1 = "".join(ssh_new.execute("cat /root/restart_marker_1.txt")) + r2 = "".join(ssh_new.execute("cat /root/restart_marker_2.txt")) + self.assertIn("restart-test-1", r1, + "Marker written before the restart is missing from the restore") + self.assertIn("restart-test-2", r2, + "Marker written after the restart (captured by the post-restart incremental) " + "is missing from the restore") + finally: + for b in reversed(backups): + try: + Backup.delete(self.apiclient, b.id) + except Exception: + pass + self._set_full_every(original_full_every) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_restore_from_incremental(self): + """ + Take FULL + 2 INCREMENTAL backups, each with a marker file. Restore from the + latest incremental and verify all three markers are present (chain flatten). + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + original_full_every = self._get_full_every() + self._set_full_every(5) + try: + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("touch /root/marker_full.txt") + Backup.create(self.apiclient, self.vm.id, "rfi_full") + time.sleep(3) + + ssh_client_vm.execute("touch /root/marker_inc1.txt") + Backup.create(self.apiclient, self.vm.id, "rfi_inc1") + time.sleep(3) + + ssh_client_vm.execute("touch /root/marker_inc2.txt") + Backup.create(self.apiclient, self.vm.id, "rfi_inc2") + time.sleep(3) + + backups = Backup.list(self.apiclient, self.vm.id) + backups.sort(key=lambda b: b.created) + self.assertEqual(len(backups), 3) + self.assertEqual(self._backup_type(backups[0]).upper(), 'FULL') + self.assertEqual(self._backup_type(backups[2]).upper(), 'INCREMENTAL') + + new_vm_name = "vm-from-inc-" + str(int(time.time())) + new_vm = Backup.createVMFromBackup(self.apiclient, self.services["small"], + mode=self.services["mode"], backupid=backups[2].id, vmname=new_vm_name, + accountname=self.account.name, domainid=self.account.domainid, + zoneid=self.zone.id) + self.cleanup.append(new_vm) + + ssh_new = new_vm.get_ssh_client(reconnect=True) + for marker in ("marker_full.txt", "marker_inc1.txt", "marker_inc2.txt"): + result = ssh_new.execute("ls /root/%s" % marker) + self.assertIn(marker, result[0], + "Restored VM should have %s (chain flattened correctly)" % marker) + + for b in reversed(backups): + Backup.delete(self.apiclient, b.id) + finally: + self._set_full_every(original_full_every) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_delete_middle_incremental_repairs_chain(self): + """ + Delete a MIDDLE incremental from a FULL -> INC1 -> INC2 chain. + The chain repair should rebase INC2 onto FULL, and the final restore + should still produce a working VM with all expected blocks. + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + original_full_every = self._get_full_every() + self._set_full_every(5) + try: + ssh_client_vm = self.vm.get_ssh_client(reconnect=True) + ssh_client_vm.execute("touch /root/dmi_full.txt") + Backup.create(self.apiclient, self.vm.id, "dmi_full") + time.sleep(3) + ssh_client_vm.execute("touch /root/dmi_inc1.txt") + Backup.create(self.apiclient, self.vm.id, "dmi_inc1") + time.sleep(3) + ssh_client_vm.execute("touch /root/dmi_inc2.txt") + Backup.create(self.apiclient, self.vm.id, "dmi_inc2") + time.sleep(3) + + backups = Backup.list(self.apiclient, self.vm.id) + backups.sort(key=lambda b: b.created) + full, inc1, inc2 = backups[0], backups[1], backups[2] + + # Delete the middle incremental — should succeed via chain repair (no force needed) + Backup.delete(self.apiclient, inc1.id) + remaining = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(remaining), 2, "After deleting middle inc, two backups should remain") + + # Restore from the remaining tail (formerly inc2) — must still produce a usable VM + new_vm_name = "vm-after-mid-del-" + str(int(time.time())) + new_vm = Backup.createVMFromBackup(self.apiclient, self.services["small"], + mode=self.services["mode"], backupid=inc2.id, vmname=new_vm_name, + accountname=self.account.name, domainid=self.account.domainid, + zoneid=self.zone.id) + self.cleanup.append(new_vm) + ssh_new = new_vm.get_ssh_client(reconnect=True) + # Both the FULL marker and (importantly) the deleted-INC1 marker should still + # be present, because the rebase merged INC1's blocks into INC2. + for marker in ("dmi_full.txt", "dmi_inc1.txt", "dmi_inc2.txt"): + result = ssh_new.execute("ls /root/%s" % marker) + self.assertIn(marker, result[0], + "After mid-incremental delete and rebase, %s should still be restorable" % marker) + + Backup.delete(self.apiclient, inc2.id) + Backup.delete(self.apiclient, full.id) + finally: + self._set_full_every(original_full_every) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_delete_full_with_children_is_deferred(self): + """ + Deleting a FULL that has surviving incrementals succeeds but is deferred: the + FULL is hidden from the backup list while its child survives, and it is + physically swept once the last descendant is deleted. + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + original_full_every = self._get_full_every() + self._set_full_every(5) + try: + Backup.create(self.apiclient, self.vm.id, "rdc_full") + time.sleep(3) + Backup.create(self.apiclient, self.vm.id, "rdc_inc") + time.sleep(3) + + backups = Backup.list(self.apiclient, self.vm.id) + backups.sort(key=lambda b: b.created) + full, inc = backups[0], backups[1] + + # Non-forced delete of the FULL is accepted but deferred: the FULL is + # tombstoned (Hidden) and disappears from the list, the child survives. + Backup.delete(self.apiclient, full.id) + remaining = Backup.list(self.apiclient, self.vm.id) + self.assertEqual(len(remaining), 1, + "Deleting a FULL with children should hide the FULL and keep the child") + self.assertEqual(remaining[0].id, inc.id, + "The surviving backup should be the incremental child") + + # Deleting the last descendant sweeps the delete-pending FULL with it. + Backup.delete(self.apiclient, inc.id) + remaining = Backup.list(self.apiclient, self.vm.id) + self.assertIsNone(remaining, "Deleting the leaf should sweep the hidden FULL, removing the chain") + finally: + self._set_full_every(original_full_every) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) + + @attr(tags=["advanced", "backup"], required_hardware="true") + def test_stopped_vm_falls_back_to_full(self): + """ + When a backup is requested while the VM is stopped, even if the chain cadence + would call for an incremental, the agent must fall back to a full and start a + new chain. The incrementalFallback flag should be reflected in backup.type=FULL. + """ + self.backup_offering.assignOffering(self.apiclient, self.vm.id) + original_full_every = self._get_full_every() + self._set_full_every(2) # next backup after the first should be incremental + try: + Backup.create(self.apiclient, self.vm.id, "svf_first") + time.sleep(3) + + # Stop the VM and trigger another backup — should fall back to FULL + self.vm.stop(self.apiclient) + time.sleep(5) + Backup.create(self.apiclient, self.vm.id, "svf_second") + time.sleep(3) + + backups = Backup.list(self.apiclient, self.vm.id) + backups.sort(key=lambda b: b.created) + self.assertEqual(len(backups), 2) + self.assertEqual(self._backup_type(backups[0]).upper(), 'FULL') + self.assertEqual(self._backup_type(backups[1]).upper(), 'FULL', + "Stopped-VM backup must be a FULL even when cadence would have asked for an INCREMENTAL") + + self.vm.start(self.apiclient) + for b in reversed(backups): + Backup.delete(self.apiclient, b.id) + finally: + self._set_full_every(original_full_every) + self.backup_offering.removeOffering(self.apiclient, self.vm.id) 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_deploy_vgpu_enabled_vm.py b/test/integration/smoke/test_deploy_vgpu_enabled_vm.py index 7ed4c8a08ca1..131ea6195ab9 100644 --- a/test/integration/smoke/test_deploy_vgpu_enabled_vm.py +++ b/test/integration/smoke/test_deploy_vgpu_enabled_vm.py @@ -24,7 +24,7 @@ # base - contains all resources as entities and defines create, delete, # list operations on them -from marvin.lib.base import Account, VirtualMachine, ServiceOffering, NetworkOffering, Network, Template +from marvin.lib.base import Account, Host, Capacities, VirtualMachine, ServiceOffering, NetworkOffering, Network, Template, GpuDevice # utils - utility classes for common cleanup, external library wrappers etc from marvin.lib.utils import cleanup_resources, get_hypervisor_type, validateList @@ -44,33 +44,35 @@ class TestDeployvGPUenabledVM(cloudstackTestCase): Test deploy a vGPU enabled VM into a user account """ @classmethod - def setUpClass(self): - testClient = super(TestDeployvGPUenabledVM, self).getClsTestClient() - self.apiclient = testClient.getApiClient() - self.testdata = self.testClient.getParsedTestDataConfig() - self.hostConfig = self.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ - self._cleanup = [] - self.unsupportedHypervisor = False - self.noSuitableHost = False - # Need to add check whether zone containing the xen hypervisor or not - # as well + def setUpClass(cls): + testClient = super(TestDeployvGPUenabledVM, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.testdata = cls.testClient.getParsedTestDataConfig() + cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + cls._cleanup = [] + cls.unsupportedHypervisor = False + cls.noSuitableHost = False + cls.hypervisor = testClient.getHypervisorInfo() + + if cls.hypervisor.lower() not in ["xenserver", "vmware", "kvm", "simulator"]: + cls.unsupportedHypervisor = True + cls.skipTest("Skipping test because suitable hypervisor/host not present") hosts = list_hosts( - self.apiclient, - hypervisor="XenServer" + cls.apiclient, + type='routing' ) if hosts is None: - # GPU feature is supported only on XenServer.Check listhosts response - self.unsupportedHypervisor = True + cls.unsupportedHypervisor = True return - else: - gpuhosts = 0 + gpuhosts = 0 + if cls.hypervisor.lower() in ["xenserver"]: for ghost in hosts: - if ghost.hypervisorversion >= "6.2.0": + if ghost and ghost.hypervisorversion >= "6.2.0": sshClient = SshClient( host=ghost.ipaddress, - port=self.testdata['configurableData']['host']["publicport"], - user=self.hostConfig['username'], - passwd=self.hostConfig['password']) + port=cls.testdata['configurableData']['host']["publicport"], + user=cls.hostConfig['username'], + passwd=cls.hostConfig['password']) if ghost.hypervisorversion == "6.2.0": res = sshClient.execute( "xe patch-list uuid=0850b186-4d47-11e3-a720-001b2151a503") @@ -82,9 +84,25 @@ def setUpClass(self): gpuhosts = gpuhosts + 1 else: continue + elif cls.hypervisor.lower() == "kvm" or cls.hypervisor.lower() == "simulator": + # Check if the host has a GPU + for host in hosts: + h = Host(host.__dict__) + h.discoverGpuDevices(cls.apiclient) + + hosts = list_hosts( + cls.apiclient + ) + for host in hosts: + if host.gputotal != None and host.gputotal > 0: + gpuhosts = gpuhosts + 1 + elif cls.hypervisor.lower() in ["vmware"]: + # + cls.noSuitableHost = True + if gpuhosts == 0: # No XenServer available with GPU Drivers installed - self.noSuitableHost = True + cls.noSuitableHost = True return def setUp(self): @@ -92,11 +110,10 @@ def setUp(self): self.apiclient = self.testClient.getApiClient() self.dbclient = self.testClient.getDbConnection() if self.noSuitableHost or self.unsupportedHypervisor: - self.hypervisor = get_hypervisor_type(self.apiclient) - if self.hypervisor.lower() not in ["vmware"]: - self.skipTest("Skipping test because suitable hypervisor/host not\ - present") - self.testdata = self.testClient.getParsedTestDataConfig() + self.skipTest("Skipping test because suitable hypervisor/host not\ + present") + self.hypervisor = get_hypervisor_type(self.apiclient) + self.testdata = self.testClient.getParsedTestDataConfig() self.cleanup = [] @@ -110,6 +127,8 @@ def setUp(self): domainid=self.domain.id ) + self.gpu_capacity_total, self.gpu_capacity_used = self.get_gpu_capacity() + if self.hypervisor.lower() in ["xenserver"]: # Before running this test for Xen Server, register a windows template with ostype as @@ -123,39 +142,39 @@ def setUp(self): self.testdata["mode"] = self.zone.networktype if self.template == FAILED: - assert False, "get_template() failed to return template with description %s" % self.testdata[ - "ostype"] + assert False, "get_template() failed to return template with description %s" % self.testdata[ + "ostype"] self.testdata["small"]["zoneid"] = self.zone.id self.testdata["small"]["template"] = self.template.id self.testdata["service_offerings"]["vgpu260qwin"]["serviceofferingdetails"] = [ - { - 'pciDevice': 'Group of NVIDIA Corporation GK107GL [GRID K1] GPUs'}, { - 'vgpuType': 'GRID K120Q'}] - # create a service offering + { + 'pciDevice': 'Group of NVIDIA Corporation GK107GL [GRID K1] GPUs'}, { + 'vgpuType': 'GRID K120Q'}] + # create a service offering self.service_offering = ServiceOffering.create( - self.apiclient, - self.testdata["service_offerings"]["vgpu260qwin"], - ) + self.apiclient, + self.testdata["service_offerings"]["vgpu260qwin"], + ) self.cleanup.append(self.service_offering) elif self.hypervisor.lower() in ["vmware"]: self.testdata["isolated_network"]["zoneid"] = self.zone.id self.userapiclient = self.testClient.getUserApiClient( - UserName=self.account.name, - DomainName=self.account.domain - ) + UserName=self.account.name, + DomainName=self.account.domain + ) self.service_offering = ServiceOffering.create( - self.apiclient, - self.testdata["service_offering"]) + self.apiclient, + self.testdata["service_offering"]) # Create Shared Network Offering self.isolated_network_offering = NetworkOffering.create( - self.apiclient, - self.testdata["isolated_network_offering"]) - # Enable Isolated Network offering + self.apiclient, + self.testdata["isolated_network_offering"]) + # Enable Isolated Network offering self.isolated_network_offering.update(self.apiclient, state='Enabled') # Register a private template in the account with nic adapter vmxnet3 @@ -169,9 +188,51 @@ def setUp(self): details=[{"mks.enable3d" : "true", "mks.use3dRenderer" : "automatic", "svga.autodetect" : "false", "svga.vramSize" : "131072"}] ) + elif self.hypervisor.lower() in ["kvm", "simulator"]: + self.template = get_template( + self.apiclient, + self.zone.id, + self.testdata["ostype"]) + self.cleanup.append(self.template) + + # 1. Fetch available vgpu profile IDs from gpu devices on hosts + # 2. Create a service offering with vgpu profile ID + vgpu_profile_id_count_map = {} + vgpu_profile_id_device_map = {} + devices = GpuDevice.list(self.apiclient) + for device in devices: + if (device.state.lower() == "free" and device.managedstate.lower() == "managed" and device.gpudevicetype.lower() != "vgpuonly"): + vgpu_profile_id_count_map[device.vgpuprofileid] = vgpu_profile_id_count_map.get(device.vgpuprofileid, 0) + 1 + vgpu_profile_id_device_map[device.vgpuprofileid] = device + if len(vgpu_profile_id_count_map) == 0: + self.skipTest("No GPU devices found on the host with state 'Free' and managed state 'Managed'") + else: + self.vgpu_profile_id = max(vgpu_profile_id_count_map, key=vgpu_profile_id_count_map.get) + self.vgpu_profile_name = vgpu_profile_id_device_map[self.vgpu_profile_id].vgpuprofilename + self.gpu_card_id = vgpu_profile_id_device_map[self.vgpu_profile_id].gpucardid + self.gpu_card_name = vgpu_profile_id_device_map[self.vgpu_profile_id].gpucardname + self.testdata["service_offering"]["vgpuprofileid"] = self.vgpu_profile_id + self.testdata["service_offering"]["gpucount"] = 1 + + self.service_offering = ServiceOffering.create( + self.apiclient, + self.testdata["service_offering"]) + self.cleanup.append(self.service_offering) + else: + self.skipTest("Skipping test because suitable hypervisor/host not\ + present") - @attr(tags=['advanced', 'basic', 'vgpu'], required_hardware="true") + def get_gpu_capacity(self): + """Get GPU capacity for the host + """ + capacities = Capacities.list(self.apiclient, fetchlatest=True) + for c in capacities: + if c.name == "GPU": + return c.capacitytotal, c.capacityused + return 0, 0 + + @attr(tags=['advanced', 'basic', 'vgpu']) def test_deploy_vgpu_enabled_vm(self): """Test Deploy Virtual Machine @@ -180,18 +241,20 @@ def test_deploy_vgpu_enabled_vm(self): # 2. Virtual Machine is vGPU enabled (via SSH) # 3. listVirtualMachines returns accurate information """ - if self.hypervisor.lower() not in ["xenserver"]: + if self.hypervisor.lower() not in ["xenserver", "kvm", "simulator"]: self.cleanup.append(self.account) self.skipTest("This test case is written specifically\ - for XenServer hypervisor") + for XenServer, KVM & Simulator hypervisor") self.virtual_machine = VirtualMachine.create( self.apiclient, - self.testdata["small"], + self.testdata["virtual_machine"], + hypervisor=self.hypervisor, accountid=self.account.name, domainid=self.account.domainid, serviceofferingid=self.service_offering.id, - mode=self.testdata['mode'] + templateid=self.template.id, + zoneid=self.zone.id ) self.cleanup.append(self.virtual_machine) @@ -199,10 +262,7 @@ def test_deploy_vgpu_enabled_vm(self): self.apiclient, id=self.virtual_machine.id) - self.debug( - "Verify listVirtualMachines response for virtual machine: %s" - % self.virtual_machine.id - ) + self.debug("Verify listVirtualMachines response for virtual machine: %s" % self.virtual_machine.id) self.assertEqual( isinstance(list_vms, list), @@ -231,31 +291,53 @@ def test_deploy_vgpu_enabled_vm(self): "Running", msg="VM is not in Running state" ) - hosts = list_hosts( - self.apiclient, - id=vm.hostid + + # Check capacity changes + total, used = self.get_gpu_capacity() + + self.assertEqual( + total, + self.gpu_capacity_total, + "Total GPU capacity did not change after VM deployment" ) - hostip = hosts[0].ipaddress - try: - sshClient = SshClient( - host=hostip, - port=self.testdata['configurableData']['host']["publicport"], - user=self.testdata['configurableData']['host']["username"], - passwd=self.testdata['configurableData']['host']["password"]) - res = sshClient.execute( - "xe vgpu-list vm-name-label=%s params=type-uuid %s" % - (vm.instancename)) - self.debug("SSH result: %s" % res) - except Exception as e: - self.fail("SSH Access failed for %s: %s" % - (hostip, e) - ) - result = str(res) self.assertEqual( - result.count("type-uuid"), - 1, - "VM is vGPU enabled." + used, + self.gpu_capacity_used + 1, + "Used GPU capacity did not change by 1 after VM deployment" ) + + if self.hypervisor.lower() in ["xenserver"]: + hosts = list_hosts( + self.apiclient, + id=vm.hostid + ) + hostip = hosts[0].ipaddress + try: + sshClient = SshClient( + host=hostip, + port=self.testdata['configurableData']['host']["publicport"], + user=self.testdata['configurableData']['host']["username"], + passwd=self.testdata['configurableData']['host']["password"]) + res = sshClient.execute( + "xe vgpu-list vm-name-label=%s params=type-uuid %s" % + (vm.instancename)) + self.debug("SSH result: %s" % res) + except Exception as e: + self.fail("SSH Access failed for %s: %s" % + (hostip, e) + ) + result = str(res) + self.assertEqual( + result.count("type-uuid"), + 1, + "VM is vGPU enabled." + ) + elif self.hypervisor.lower() in ["kvm", "simulator"]: + self.assertEqual(self.virtual_machine.vgpuprofileid, self.vgpu_profile_id, "VM is vGPU enabled.") + self.assertEqual(self.virtual_machine.gpucount, 1, "VM is vGPU enabled.") + self.assertEqual(self.virtual_machine.gpucardid, self.gpu_card_id, "VM is vGPU enabled.") + self.assertEqual(self.virtual_machine.gpucardname, self.gpu_card_name, "VM is vGPU enabled.") + self.assertEqual(self.virtual_machine.vgpuprofilename, self.vgpu_profile_name, "VM is vGPU enabled.") self.cleanup.append(self.account) def tearDown(self): @@ -319,7 +401,7 @@ def test_3d_gpu_support(self): qresultset = self.dbclient.execute("select id from vm_instance where uuid = '%s';" % self.virtual_machine.id) vm_id = qresultset[0] - qresultset = self.dbclient.execute("select name, value from user_vm_details where vm_id = '%d';" % vm_id) + qresultset = self.dbclient.execute("select name, value from vm_instance_details where vm_id = '%d';" % vm_id) detailKeys = [x[0] for x in qresultset] self.assertTrue('mks.enable3d' in detailKeys and 'mks.use3dRenderer' in detailKeys and 'svga.autodetect' in detailKeys and 'svga.vramSize' in detailKeys, "VM details do not contain 3D GPU details") diff --git a/test/integration/smoke/test_deploy_vm_iso.py b/test/integration/smoke/test_deploy_vm_iso.py index 33d4261c9d56..039ba7999156 100644 --- a/test/integration/smoke/test_deploy_vm_iso.py +++ b/test/integration/smoke/test_deploy_vm_iso.py @@ -20,7 +20,6 @@ # Import Local Modules from nose.plugins.attrib import attr from marvin.cloudstackTestCase import cloudstackTestCase -from marvin.lib.utils import cleanup_resources from marvin.lib.base import (Account, VirtualMachine, ServiceOffering, @@ -38,49 +37,42 @@ class TestDeployVMFromISO(cloudstackTestCase): def setUpClass(cls): cls.testClient = super(TestDeployVMFromISO, cls).getClsTestClient() - cls.api_client = cls.testClient.getApiClient() + cls.apiclient = cls.testClient.getApiClient() cls.testdata = cls.testClient.getParsedTestDataConfig() # Get Zone, Domain and templates - cls.domain = get_domain(cls.api_client) - cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) cls.hypervisor = cls.testClient.getHypervisorInfo() + cls._cleanup = [] cls.template = get_test_template( - cls.api_client, + cls.apiclient, cls.zone.id, cls.hypervisor ) # Create service, disk offerings etc cls.service_offering = ServiceOffering.create( - cls.api_client, + cls.apiclient, cls.testdata["service_offering"] ) + cls._cleanup.append(cls.service_offering) cls.disk_offering = DiskOffering.create( - cls.api_client, + cls.apiclient, cls.testdata["disk_offering"] ) + cls._cleanup.append(cls.disk_offering) - cls._cleanup = [ - cls.service_offering, - cls.disk_offering - ] return - @classmethod - def tearDownClass(cls): - try: - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - def setUp(self): self.apiclient = self.testClient.getApiClient() self.dbclient = self.testClient.getDbConnection() self.hypervisor = self.testClient.getHypervisorInfo() + self.cleanup = [] self.testdata["virtual_machine"]["zoneid"] = self.zone.id self.testdata["virtual_machine"]["template"] = self.template.id self.testdata["iso"]["zoneid"] = self.zone.id @@ -89,38 +81,27 @@ def setUp(self): self.testdata["account"], domainid=self.domain.id ) - self.cleanup = [self.account] + self.cleanup.append(self.account) return - def tearDown(self): - try: - self.debug("Cleaning up the resources") - cleanup_resources(self.apiclient, self.cleanup) - self.debug("Cleanup complete!") - except Exception as e: - self.debug("Warning! Exception in tearDown: %s" % e) - @attr( tags=[ "advanced", "eip", "advancedns", "basic", - "sg"], - required_hardware="true") + "sg" + ], + required_hardware="true" + ) def test_deploy_vm_from_iso(self): """Test Deploy Virtual Machine from ISO """ # Validate the following: - # 1. deploy VM using ISO - # 2. listVM command should return the deployed VM. State of this VM - # should be "Running". - self.hypervisor = self.testClient.getHypervisorInfo() - if self.hypervisor.lower() in ['lxc']: - self.skipTest( - "vm deploy from ISO feature is not supported on %s" % - self.hypervisor.lower()) + # 1. Create an ISO + # 2. Deploy a VM from the ISO + # 3. VM should be in 'Running' state self.iso = Iso.create( self.apiclient, @@ -129,6 +110,19 @@ def test_deploy_vm_from_iso(self): domainid=self.account.domainid, zoneid=self.zone.id ) + self.cleanup.append(self.iso) + + self.debug("ISO created with ID: %s" % self.iso.id) + list_iso_response = Iso.list( + self.apiclient, + id=self.iso.id + ) + while not isinstance(list_iso_response, list): + list_iso_response = Iso.list( + self.apiclient, + id=self.iso.id + ) + try: # Download the ISO self.iso.download(self.apiclient) @@ -136,9 +130,8 @@ def test_deploy_vm_from_iso(self): raise Exception("Exception while downloading ISO %s: %s" % (self.iso.id, e)) - self.debug("Registered ISO: %s" % self.iso.name) - self.debug("Deploying instance in the account: %s" % - self.account.name) + self.debug(f"Registered ISO: {self.iso.name}") + self.debug(f"Deploying instance in the account: {self.account.name}") self.virtual_machine = VirtualMachine.create( self.apiclient, self.testdata["virtual_machine"], @@ -149,9 +142,34 @@ def test_deploy_vm_from_iso(self): diskofferingid=self.disk_offering.id, hypervisor=self.hypervisor ) + self.cleanup.append(self.virtual_machine) + + self.debug("VM created with ID: %s" % self.virtual_machine.id) + + list_vm_response = VirtualMachine.list( + self.apiclient, + id=self.virtual_machine.id + ) + + self.assertEqual( + isinstance(list_vm_response, list), + True, + "Check list response returns a valid list" + ) + vm_response = list_vm_response[0] + vm_state = self.virtual_machine.getState( + self.apiclient, + VirtualMachine.RUNNING + ) response = self.virtual_machine.getState( self.apiclient, VirtualMachine.RUNNING) self.assertEqual(response[0], PASS, response[1]) + + self.assertEqual( + vm_response.isoid, + self.iso.id, + "Check virtual machine is booted from the ISO" + ) return diff --git a/test/integration/smoke/test_dns_framework_powerdns.py b/test/integration/smoke/test_dns_framework_powerdns.py new file mode 100644 index 000000000000..7df8bb984ea3 --- /dev/null +++ b/test/integration/smoke/test_dns_framework_powerdns.py @@ -0,0 +1,401 @@ +# 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. + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import * +from nose.plugins.attrib import attr + +import subprocess +import time +import logging +import socket + +class TestCloudStackDNSFramework(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + """ + Pre-requisite: + Bring up PDNS via docker compose (external dependency for DNS provider). + """ + super(TestCloudStackDNSFramework, cls).setUpClass() + cls.api_client = cls.testClient.getApiClient() + cls.pdns_unavailable = False + + cls.logger = logging.getLogger("TestCloudStackDNSFramework") + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + # ------------------------- + # Detect Marvin VM IP (reachable by MS) + # ------------------------- + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + try: + s.connect(("8.8.8.8", 80)) + cls.marvin_vm_ip = s.getsockname()[0] + finally: + s.close() + + cls.logger.info(f"Detected Marvin VM IP: {cls.marvin_vm_ip}") + + # ------------------------- + # PDNS compose config + # ------------------------- + + cls.compose_file = "/marvin/pdns/docker-compose.yml" + cls.compose_dir = "/marvin/pdns" + cls.logger.info("Bringing up PDNS via docker compose...") + + up_cmd = [ + "docker", "compose", + "-f", cls.compose_file, + "up", "-d" + ] + + result = subprocess.run( + up_cmd, + cwd=cls.compose_dir, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + if result.returncode != 0: + cls.tearDownClass() + raise Exception(f"Failed to start PDNS:\n{result.stderr}") + + # Wait for PDNS to be ready with polling + cls.pdns_url = f"http://{cls.marvin_vm_ip}" + cls.logger.info(f"PDNS endpoint: {cls.pdns_url}:8081") + cls._wait_for_pdns_ready() + + @classmethod + def _wait_for_pdns_ready(cls, timeout=90, interval=1): + """ + Poll PDNS API until it responds or timeout is reached. + Logs a message and sets cls.pdns_unavailable if PDNS is not reachable. + """ + import urllib.request + api_url = f"{cls.pdns_url}:8081/api/v1/servers" + cls.logger.info(f"Waiting for PDNS to be ready at {api_url} (timeout: {timeout}s)") + for _ in range(timeout // interval): + try: + req = urllib.request.Request(api_url) + req.add_header("X-API-Key", "supersecretapikey") + resp = urllib.request.urlopen(req, timeout=2) + if resp.status == 200: + cls.logger.info("PDNS is up and running") + cls.pdns_unavailable = False + return + except Exception: + pass + time.sleep(interval) + + cls.logger.warning("PDNS did not become ready within timeout; skipping tests") + cls.pdns_unavailable = True + + def setUp(self): + if self.__class__.pdns_unavailable: + import unittest + raise unittest.SkipTest("PDNS is unavailable; skipping test") + + + @attr(tags=["advanced"], required_hardware="true") + def test_01_list_dns_providers(self): + """ + List DNS providers, expect PowerDNS provider to be present + """ + list_providers_cmd = listDnsProviders.listDnsProvidersCmd() + self.logger.info("Listing DNS providers to verify PowerDNS presence") + response = self.api_client.listDnsProviders(list_providers_cmd) + self.assertIsNotNone(response, "Failed to list DNS providers") + self.logger.info(f"DNS Providers found: {[provider.name for provider in response]}") + + + @attr(tags=["advanced"], required_hardware="true") + def test_02_add_dns_server(self): + """ + Register PDNS as DNS provider in CloudStack + """ + self.logger.info("Adding PDNS DNS server") + + response = self._add_dns_server() + self.assertIsNotNone(response, "Failed to add DNS provider") + self.__class__.dns_server_id = response.id + self.logger.info(f"DNS Provider added: {response.id}") + self.assertIsNotNone(response.id, "DNS server ID should not be None") + + + @attr(tags=["advanced"], required_hardware="true") + def test_03_list_dns_servers(self): + """ + List DNS servers and verify the newly added PDNS provider is present + """ + self.logger.info("Listing DNS servers to verify addition") + list_cmd = listDnsServers.listDnsServersCmd() + list_cmd.id = self.dns_server_id + response = self.api_client.listDnsServers(list_cmd) + self.assertIsNotNone(response, "Failed to list DNS servers") + self.assertEqual(len(response), 1, "Expected exactly one DNS server") + self.assertEqual(response[0].id, self.dns_server_id, "DNS server ID mismatch") + + + @attr(tags=["advanced"], required_hardware="true") + def test_04_create_dns_zone(self): + """ + Create a DNS zone in the added PDNS provider + """ + self.logger.info("Creating a DNS zone") + response = self._create_zone(self.dns_server_id) + self.assertIsNotNone(response, "Failed to create DNS zone") + self.assertIsNotNone(response.id, "DNS zone ID should not be None") + self.__class__.dns_zone_id = response.id + self.logger.info(f"DNS Zone created: {response.id}") + + + @attr(tags=["advanced"], required_hardware="true") + def test_05_list_dns_zones(self): + """ + List DNS zones and verify the newly created zone is present + """ + self.logger.info("Listing DNS zones to verify creation") + list_zones_cmd = listDnsZones.listDnsZonesCmd() + list_zones_cmd.id = self.dns_zone_id + response = self.api_client.listDnsZones(list_zones_cmd) + self.assertIsNotNone(response, "Failed to list DNS zones") + self.assertEqual(len(response), 1, "Expected exactly one DNS zone") + self.assertEqual(response[0].id, self.dns_zone_id, "DNS zone ID mismatch") + self.assertEqual(response[0].name, "example.com", "DNS zone name mismatch") + + @attr(tags=["advanced"], required_hardware="true") + def test_06_create_a_dns_record(self): + """ + Create a DNS record in the previously created zone + """ + self.logger.info("Creating A DNS record") + response = self._create_record( + self.dns_zone_id, + "www.example.com", + "A", + "10.1.1.10" + ) + self.assertIsNotNone(response, "Failed to create DNS record") + self.assertEqual(response.name, "www.example.com", "DNS record name mismatch") + self._assert_dns("www.example.com", "A", expected="10.1.1.10") + + @attr(tags=["advanced"], required_hardware="true") + def test_07_create_aaaa_dns_records(self): + """ + Create AAAA DNS records in the previously created zone + """ + self.logger.info("Creating AAAA DNS records") + response = self._create_record( + self.dns_zone_id, + "www.example.com", + "AAAA", + "2001:db8::10" + ) + self.assertIsNotNone(response, "Failed to create AAAA DNS record") + self.assertTrue(response.name is not None, "DNS record name should not be None") + self._assert_dns("www.example.com", "AAAA", expected="2001:db8::10") + + @attr(tags=["advanced"], required_hardware="true") + def test_08_create_mx_dns_record(self): + """ + Create an MX DNS record in the previously created zone + """ + self.logger.info("Creating an MX DNS record") + response = self._create_record( + self.dns_zone_id, + "example.com", + "MX", + "10 mail.example.com" + ) + self.assertIsNotNone(response, "Failed to create MX DNS record") + self.assertTrue(response.name is not None, "DNS record name should not be None") + self._assert_dns("example.com", "MX", contains=["10", "mail.example.com"]) + + + @attr(tags=["advanced"], required_hardware="true") + def test_09_list_dns_records(self): + """ + List DNS records in the zone and verify the created records are present + """ + self.logger.info("Listing DNS records to verify creation") + list_records_cmd = listDnsRecords.listDnsRecordsCmd() + list_records_cmd.dnszoneid = self.dns_zone_id + response = self.api_client.listDnsRecords(list_records_cmd) + self.assertIsNotNone(response, "Failed to list DNS records") + self.assertEqual(len(response), 4, "Expected four DNS records, including NS record") + record_types = set(record.type for record in response) + self.assertSetEqual(record_types, {"NS", "A", "AAAA", "MX"}, "DNS record types mismatch") + + @attr(tags=["advanced"], required_hardware="true") + def test_10_delete_dns_record(self): + """ + Delete one of the DNS records and verify it's removed + """ + self.logger.info("Deleting a DNS record") + delete_record_cmd = deleteDnsRecord.deleteDnsRecordCmd() + delete_record_cmd.name = "www.example.com" + delete_record_cmd.type = "A" + delete_record_cmd.dnszoneid = self.dns_zone_id + delete_response = self.api_client.deleteDnsRecord(delete_record_cmd) + self.assertIsNotNone(delete_response, "Failed to delete DNS record") + self.logger.info(f"DNS Record deleted: {delete_record_cmd.name}") + + # Verify deletion + list_record_cmd = listDnsRecords.listDnsRecordsCmd() + list_record_cmd.dnszoneid = self.dns_zone_id + response_after_deletion = self.api_client.listDnsRecords(list_record_cmd) + self.assertEqual(len(response_after_deletion), 3, "Expected three DNS records after deletion") + remaining_record_names = set(record.name for record in response_after_deletion) + self.assertNotIn(delete_record_cmd.name, remaining_record_names, "Deleted DNS record still present") + + @attr(tags=["advanced"], required_hardware="true") + def test_11_delete_dns_zone(self): + """ + Delete the DNS zone and verify it's removed + """ + self.logger.info("Deleting the DNS zone") + delete_zone_cmd = deleteDnsZone.deleteDnsZoneCmd() + delete_zone_cmd.id = self.dns_zone_id + response = self.api_client.deleteDnsZone(delete_zone_cmd) + self.assertIsNotNone(response, "Failed to delete DNS zone") + self.logger.info(f"DNS Zone deleted: {self.dns_zone_id}") + + # Verify deletion + list_zones_cmd = listDnsZones.listDnsZonesCmd() + list_zones_cmd.id = self.dns_zone_id + try: + self.api_client.listDnsZones(list_zones_cmd) + self.fail("DNS zone still exists after deletion") + except Exception as e: + self.logger.info(f"Expected exception after delete: {str(e)}") + + @attr(tags=["advanced"], required_hardware="true") + def test_12_delete_dns_server(self): + """ + Delete the PDNS DNS server and verify it's removed + """ + self.logger.info("Deleting the PDNS DNS server") + delete_cmd = deleteDnsServer.deleteDnsServerCmd() + delete_cmd.id = self.dns_server_id + response = self.api_client.deleteDnsServer(delete_cmd) + self.assertIsNotNone(response, "Failed to delete DNS server") + self.logger.info(f"DNS Server deleted: {self.dns_server_id}") + + # Verify deletion + list_cmd = listDnsServers.listDnsServersCmd() + list_cmd.id = self.dns_server_id + response = self.api_client.listDnsServers(list_cmd) + dns_servers = response or [] + self.assertEqual(len(dns_servers), 0, "Expected no DNS servers after deletion") + + @classmethod + def tearDownClass(cls): + """ + Stop PDNS after tests + """ + + try: + cls.logger.info("Stopping PDNS stack...") + + cmd = [ + "docker", "compose", + "-f", cls.compose_file, + "down" + ] + + subprocess.run(cmd, cwd=cls.compose_dir) + + finally: + super(TestCloudStackDNSFramework, cls).tearDownClass() + + + def _create_record(self, zone_id, name, rtype, contents): + cmd = createDnsRecord.createDnsRecordCmd() + cmd.dnszoneid = zone_id + cmd.name = name + cmd.type = rtype + cmd.contents = contents + + return self.api_client.createDnsRecord(cmd) + + + def _add_dns_server(self): + cmd = addDnsServer.addDnsServerCmd() + cmd.name = "pdns-server" + cmd.url = self.pdns_url + cmd.dnsapikey = "supersecretapikey" + cmd.provider = "PowerDNS" + cmd.nameservers = ["ns1.example.com", "ns2.example.com"] + cmd.externalserverid = "localhost" + cmd.ispublic = True + cmd.port = 8081 + cmd.publicdomainsuffix = "pdns-public.example.com" + + return self.api_client.addDnsServer(cmd) + + + def _create_zone(self, server_id): + cmd = createDnsZone.createDnsZoneCmd() + cmd.dnsserverid = server_id + cmd.name = "example.com" + cmd.description = "Test DNS Zone for PDNS" + + return self.api_client.createDnsZone(cmd) + + + def _dig(self, name, rtype): + dns_ip = self.__class__.marvin_vm_ip + dns_port = 53 + + cmd = [ + "dig", + f"@{dns_ip}", + "-p", + str(dns_port), + name, + rtype, + "+short" + ] + self.logger.info(f"Running: {' '.join(cmd)}") + result = subprocess.run( + cmd, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + self.assertEqual(result.returncode, 0, f"dig failed: {result.stderr}") + output = result.stdout.strip().replace("\n", " ") + self.logger.info(f"dig output: {output}") + return output + + + def _assert_dns(self, name, rtype, expected=None, contains=None): + output = self._dig(name, rtype) + + if expected is not None: + self.assertIn(expected, output) + + if contains: + for item in contains: + self.assertIn(item, output) + + return output diff --git a/test/integration/smoke/test_domain_vpc_offerings.py b/test/integration/smoke/test_domain_vpc_offerings.py index f3d31b2bf7e9..9570d35c618e 100644 --- a/test/integration/smoke/test_domain_vpc_offerings.py +++ b/test/integration/smoke/test_domain_vpc_offerings.py @@ -28,9 +28,16 @@ from marvin.lib.base import (Domain, VpcOffering, Account, - VPC) + VPC, + NetworkOffering, + Network, + VirtualMachine, + ServiceOffering, + PublicIPAddress, + NATRule) from marvin.lib.common import (get_domain, - get_zone) + get_zone, + get_test_template) from nose.plugins.attrib import attr import time @@ -222,6 +229,7 @@ def setUpClass(cls): cls.apiclient = testClient.getApiClient() cls.localservices = Services().services cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = cls.testClient.getHypervisorInfo() # Create domains cls.domain_1 = Domain.create( cls.apiclient, @@ -402,3 +410,158 @@ def test_03_create_vpc_domain_vpc_offering(self): self.debug("Vpc created for first child subdomain %s" % self.valid_account_3.domainid) return + + @attr( + tags=[ + "advanced", + "eip", + "sg", + "advancedns", + "smoke"], + required_hardware="false") + def test_04_validate_vpc_offering_conserve_mode_disabled(self): + """Test to create and validate vpc with conserve mode disabled for an existing domain specified vpc offering""" + + # Validate the following: + # 1. Create Vpc for user in domain for which offering is specified + # 2. Validate that conserve mode is disabled for the vpc (cannot reuse ip address on multiple VPC tiers) + + template = get_test_template( + self.apiclient, + self.zone.id, + self.hypervisor) + if template == FAILED: + assert False, "get_test_template() failed to return template" + + valid_account_1 = Account.create( + self.apiclient, + self.services["account"], + domainid=self.domain_1.id + ) + self.cleanup.append(valid_account_1) + + service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["tiny"] + ) + self.cleanup.append(service_offering) + + self.services["vpc"]["cidr"] = "10.10.20.0/24" + vpc = VPC.create( + apiclient=self.apiclient, + services=self.services["vpc"], + account=valid_account_1.name, + domainid=valid_account_1.domainid, + zoneid=self.zone.id, + vpcofferingid=self.vpc_offering.id + ) + self.debug("Vpc created for subdomain %s" % valid_account_1.domainid) + + self.services["network_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding' + self.services["network_offering"]["serviceProviderList"] = { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "NetworkACL": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter' + } + network_offering = NetworkOffering.create( + self.apiclient, + self.services["network_offering"] + ) + network_offering.update(self.apiclient, state="Enabled") + self.cleanup.append(network_offering) + + gateway_tier1 = "10.10.20.1" + netmask_tiers = "255.255.255.240" + + self.services["network_offering"]["name"] = "tier1-" + vpc.id + self.services["network_offering"]["displayname"] = "tier1-" + vpc.id + tier1 = Network.create( + self.apiclient, + services=self.services["network_offering"], + accountid=valid_account_1.name, + domainid=valid_account_1.domainid, + networkofferingid=network_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway=gateway_tier1, + netmask=netmask_tiers, + ) + + gateway_tier2 = "10.10.20.17" + self.services["network_offering"]["name"] = "tier2-" + vpc.id + self.services["network_offering"]["displayname"] = "tier2-" + vpc.id + tier2 = Network.create( + self.apiclient, + services=self.services["network_offering"], + accountid=valid_account_1.name, + domainid=valid_account_1.domainid, + networkofferingid=network_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway=gateway_tier2, + netmask=netmask_tiers, + ) + + self.services["virtual_machine"]["displayname"] = "vm1" + vpc.id + vm1 = VirtualMachine.create( + self.apiclient, + services=self.services["virtual_machine"], + templateid=template.id, + zoneid=self.zone.id, + accountid=valid_account_1.name, + domainid=valid_account_1.domainid, + serviceofferingid=service_offering.id, + networkids=[tier1.id], + ) + + self.services["virtual_machine"]["displayname"] = "vm2" + vpc.id + vm2 = VirtualMachine.create( + self.apiclient, + services=self.services["virtual_machine"], + templateid=template.id, + zoneid=self.zone.id, + accountid=valid_account_1.name, + domainid=valid_account_1.domainid, + serviceofferingid=service_offering.id, + networkids=[tier2.id], + ) + + public_ip = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + accountid=valid_account_1.name, + domainid=valid_account_1.domainid, + vpcid=vpc.id, + ) + + nat_rule = NATRule.create( + self.apiclient, + vm1, + self.services["natrule"], + ipaddressid=public_ip.ipaddress.id, + vpcid=vpc.id, + networkid=tier1.id, + ) + + self.services["natrule"]["privateport"] = 80 + self.services["natrule"]["publicport"] = 80 + try: + NATRule.create( + self.apiclient, + vm2, + self.services["natrule"], + ipaddressid=public_ip.ipaddress.id, + vpcid=vpc.id, + networkid=tier2.id, + ) + self.fail( + "Expected cross-tier rule creation to fail with conserveMode=False, but succeeded" + ) + except CloudstackAPIException as e: + self.debug("Expected cross-tier rule creation to failure with conserveMode=False") diff --git a/test/integration/smoke/test_extension_custom.py b/test/integration/smoke/test_extension_custom.py new file mode 100644 index 000000000000..1f52cb41f3e6 --- /dev/null +++ b/test/integration/smoke/test_extension_custom.py @@ -0,0 +1,619 @@ +# 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. +""" BVT tests for custom extension +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import listManagementServers +from marvin.lib.base import (Extension, + Pod, + Cluster, + Host, + Template, + ServiceOffering, + NetworkOffering, + Network, + VirtualMachine, + ExtensionCustomAction) +from marvin.lib.common import (get_zone) +from marvin.lib.utils import (random_gen) +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr +# Import System modules +import json +import logging +from pathlib import Path +import random +import string +import time + +_multiprocess_shared_ = True + +CUSTOM_EXTENSION_CONTENT = """#!/bin/bash + +parse_json() { + local json_string=$1 + echo "$json_string" | jq '.' > /dev/null || { echo '{"error":"Invalid JSON input"}'; exit 1; } +} + +get_vm_name() { + local input_json=$1 + local name + name=$(jq -r '.virtualmachinename' <<< "$input_json") + if [[ -z "$name" || "$name" == "null" ]]; then + echo '{"status":"error","message":"virtualmachinename missing in JSON"}' + exit 1 + fi + echo "$name" +} + +get_vm_file() { + local name=$1 + local script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + echo "$script_dir/$name" +} + +validate_vm_file_exists() { + local file=$1 + if [[ ! -f "$file" ]]; then + echo '{"status":"error","message":"Instance file not found"}' + exit 1 + fi +} + +update_vm_status() { + local file=$1 + local status=$2 + local updated_json + updated_json=$(jq --arg status "$status" '.status = $status' "$file") + echo "$updated_json" > "$file" +} + +prepare() { + echo "" +} + +create() { + parse_json "$1" + local vm_name updated_json file + vm_name=$(get_vm_name "$1") + file=$(get_vm_file "$vm_name") + + updated_json=$(jq '. + {status: "Running"}' <<< "$1") + echo "$updated_json" > "$file" + + jq -n --arg file "$file" \ + '{status: "success", message: "Instance created", file: $file}' +} + +delete() { + parse_json "$1" + local vm_name file + vm_name=$(get_vm_name "$1") + file=$(get_vm_file "$vm_name") + + if [[ -f "$file" ]]; then + rm -f "$file" + fi + jq -n --arg file "$file" \ + '{status: "success", message: "Instance deleted", file: $file}' +} + +start() { + parse_json "$1" + local vm_name file + vm_name=$(get_vm_name "$1") + file=$(get_vm_file "$vm_name") + validate_vm_file_exists "$file" + + update_vm_status "$file" "Running" + echo '{"status": "success", "message": "Instance started"}' +} + +stop() { + parse_json "$1" + local vm_name file + vm_name=$(get_vm_name "$1") + file=$(get_vm_file "$vm_name") + + if [[ -f "$file" ]]; then + update_vm_status "$file" "Stopped" + fi + echo '{"status": "success", "message": "Instance stopped"}' +} + +reboot() { + parse_json "$1" + local vm_name file + vm_name=$(get_vm_name "$1") + file=$(get_vm_file "$vm_name") + validate_vm_file_exists "$file" + + update_vm_status "$file" "Running" + echo '{"status": "success", "message": "Instance rebooted"}' +} + +status() { + parse_json "$1" + local vm_name file vm_status + vm_name=$(get_vm_name "$1") + file=$(get_vm_file "$vm_name") + validate_vm_file_exists "$file" + + vm_status=$(jq -r '.status' "$file") + [[ -z "$vm_status" || "$vm_status" == "null" ]] && vm_status="unknown" + + case "${vm_status,,}" in + "running"|"poweron") + power_state="PowerOn" + ;; + "stopped"|"shutdown"|"poweroff") + power_state="PowerOff" + ;; + *) + power_state="$vm_status" + ;; + esac + + jq -n --arg ps "$power_state" '{status: "success", power_state: $ps}' +} + +testaction() { + parse_json "$1" + local vm_name param_val + vm_name=$(get_vm_name "$1") + param_val=$(jq -r '.parameters.Name' <<< "$1") + + echo "$param_val for $vm_name" +} + +action=$1 +parameters_file="$2" +wait_time="$3" + +if [[ -z "$action" || -z "$parameters_file" ]]; then + echo '{"error":"Missing required arguments"}' + exit 1 +fi + +if [[ ! -r "$parameters_file" ]]; then + echo '{"error":"File not found or unreadable"}' + exit 1 +fi + +parameters=$(<"$parameters_file") + +case $action in + prepare) prepare "$parameters" ;; + create) create "$parameters" ;; + delete) delete "$parameters" ;; + start) start "$parameters" ;; + stop) stop "$parameters" ;; + reboot) reboot "$parameters" ;; + status) status "$parameters" ;; + testaction) testaction "$parameters" ;; + *) echo '{"error":"Invalid action"}'; exit 1 ;; +esac + +exit 0 + +""" + +class TestExtensions(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestExtensions, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + + cls._cleanup = [] + cls.logger = logging.getLogger('TestExtensions') + cls.logger.setLevel(logging.DEBUG) + + cls.compute_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"]) + cls._cleanup.append(cls.compute_offering) + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["l2-network_offering"], + ) + cls._cleanup.append(cls.network_offering) + cls.network_offering.update(cls.apiclient, state='Enabled') + cls.services["network"]["networkoffering"] = cls.network_offering.id + cls.l2_network = Network.create( + cls.apiclient, + cls.services["l2-network"], + zoneid=cls.zone.id, + networkofferingid=cls.network_offering.id + ) + cls._cleanup.append(cls.l2_network) + + cls.resource_name_suffix = random_gen() + cls.create_cluster() + cls.extension = Extension.create( + cls.apiclient, + name=f"ext-{cls.resource_name_suffix}", + type='Orchestrator' + ) + cls._cleanup.append(cls.extension) + cls.update_extension_path(cls.extension.path) + cls.extension.register(cls.apiclient, cls.cluster.id, 'Cluster') + details = { + 'url': f"host-{cls.resource_name_suffix}", + 'zoneid': cls.cluster.zoneid, + 'podid': cls.cluster.podid, + 'username': 'External', + 'password': 'External' + } + cls.host = Host.create( + cls.apiclient, + cls.cluster, + details, + hypervisor=cls.cluster.hypervisortype + ) + cls._cleanup.append(cls.host) + template_name = f"template-{cls.resource_name_suffix}" + template_data = { + "name": template_name, + "displaytext": template_name, + "format": cls.host.hypervisor, + "hypervisor": cls.host.hypervisor, + "ostype": "Other Linux (64-bit)", + "url": template_name, + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "extensionid": cls.extension.id + } + cls.template = Template.register( + cls.apiclient, + template_data, + zoneid=cls.zone.id, + hypervisor=cls.cluster.hypervisortype + ) + cls._cleanup.append(cls.template) + logging.info("Waiting for 3 seconds for template to be ready") + time.sleep(3) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["virtual_machine"]["template"] = cls.template.id + + @classmethod + def tearDownClass(cls): + super(TestExtensions, cls).tearDownClass() + + @classmethod + def create_cluster(cls): + pod_list = Pod.list(cls.apiclient) + if len(pod_list) <= 0: + cls.fail("No Pods found") + pod_id = pod_list[0].id + cluster_services = { + 'clustername': 'cluster-' + cls.resource_name_suffix, + 'clustertype': 'CloudManaged' + } + cls.cluster = Cluster.create( + cls.apiclient, + cluster_services, + cls.zone.id, + pod_id, + 'External' + ) + cls._cleanup.append(cls.cluster) + + @classmethod + def getManagementServerIps(cls): + if cls.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + return None + cmd = listManagementServers.listManagementServersCmd() + servers = cls.apiclient.listManagementServers(cmd) + active_server_ips = [] + active_server_ips.append(cls.mgtSvrDetails["mgtSvrIp"]) + for idx, server in enumerate(servers): + if server.state == 'Up' and server.ipaddress != cls.mgtSvrDetails["mgtSvrIp"]: + active_server_ips.append(server.ipaddress) + return active_server_ips + + @classmethod + def update_path_locally(cls, path): + try: + file = Path(path) + file.write_text(CUSTOM_EXTENSION_CONTENT) + file.chmod(file.stat().st_mode | 0o111) # Make executable + except Exception as e: + cls.fail(f"Failed to update path on localhost: {str(e)}") + + @classmethod + def update_extension_path(cls, path): + logging.info(f"Updating extension path {path}") + server_ips = cls.getManagementServerIps() + if server_ips is None: + if cls.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + cls.update_path_locally(path) + return + cls.fail(f"Extension path update cannot be done on {cls.mgtSvrDetails['mgtSvrIp']}") + logging.info("Updating extension path on all management server") + command = ( + f"cat << 'EOF' > {path}\n{CUSTOM_EXTENSION_CONTENT}\nEOF\n" + f"chmod +x {path}" + ) + for idx, server_ip in enumerate(server_ips): + logging.info(f"Updating extension path on management server #{idx} with IP {server_ip}") + sshClient = SshClient( + server_ip, + 22, + cls.mgtSvrDetails["user"], + cls.mgtSvrDetails["passwd"] + ) + sshClient.execute(command) + + def setUp(self): + self.cleanup = [] + + def tearDown(self): + super(TestExtensions, self).tearDown() + + def get_vm_content_path(self, extension_path, vm_name): + directory = extension_path.rsplit('/', 1)[0] if '/' in extension_path else '.' + path = f"{directory}/{vm_name}" + return path + + def get_vm_content_locally(self, path): + try: + file = Path(path) + if not file.exists(): + return None + return file.read_text().strip() + except Exception: + return None + + def get_vm_content(self, extension_path, vm_name): + path = self.get_vm_content_path(extension_path, vm_name) + server_ips = self.getManagementServerIps() + if not server_ips: + if self.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + return self.get_vm_content_locally(path) + return None + logging.info("Trying to get VM content from all management server") + command = f"cat {path}" + for idx, server_ip in enumerate(server_ips): + logging.info(f"Trying to get VM content from management server #{idx} with IP {server_ip}") + sshClient = SshClient( + server_ip, + 22, + self.mgtSvrDetails["user"], + self.mgtSvrDetails["passwd"] + ) + results = sshClient.execute(command) + if isinstance(results, list) and len(results) > 0: + return '\n'.join(line.strip() for line in results) + return None + + def check_vm_content_values(self, content): + if content is None: + self.fail("VM content is empty") + + try: + data = json.loads(content) + except json.JSONDecodeError as e: + self.fail(f"Invalid JSON for the VM: {e}") + + if not data: + self.fail("Empty JSON for the VM") + + required_keys = ['externaldetails', 'cloudstack.vm.details', 'virtualmachineid', 'virtualmachinename', 'status'] + if not all(key in data for key in required_keys): + self.fail("Missing one or more required keys.") + + memory = self.compute_offering.memory * 1024 * 1024 + vm_details = data['cloudstack.vm.details'] + + self.assertEqual( + memory, + vm_details['minRam'], + "VM memory mismatch" + ) + self.assertEqual( + self.compute_offering.cpunumber, + vm_details['cpus'], + "VM CPU count mismatch" + ) + self.assertEqual( + 'Running', + data['status'], + "VM status mismatch" + ) + + def check_vm_status_from_content(self, extension_path, vm_name, status): + content = self.get_vm_content(extension_path, vm_name) + if content is None: + self.fail("VM content is empty") + + try: + data = json.loads(content) + except json.JSONDecodeError as e: + self.fail(f"Invalid JSON for the VM: {e}") + + if not data: + self.fail("Empty JSON for the VM") + + self.assertEqual( + status, + data['status'], + "VM status mismatch" + ) + + def check_vm_content_exist_locally(self, path): + try: + file = Path(path) + return file.exists() + except Exception: + return None + + def check_vm_content_exist(self, extension_path, vm_name): + path = self.get_vm_content_path(extension_path, vm_name) + server_ips = self.getManagementServerIps() + if not server_ips: + if self.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + return self.check_vm_content_exist_locally(path) + return None + logging.info("Trying to check VM content exists from all management server") + command = f"test -e '{path}' && echo EXISTS || echo MISSING" + for idx, server_ip in enumerate(server_ips): + logging.info(f"Trying to check VM content exists from management server #{idx} with IP {server_ip}") + sshClient = SshClient( + server_ip, + 22, + self.mgtSvrDetails["user"], + self.mgtSvrDetails["passwd"] + ) + results = sshClient.execute(command) + if results is not None and results[0].strip() == "EXISTS": + return True + return False + + def popItemFromCleanup(self, item_id): + for idx, x in enumerate(self.cleanup): + if x.id == item_id: + self.cleanup.pop(idx) + break + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_01_extension_vm_lifecycle(self): + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.compute_offering.id, + networkids=[self.l2_network.id] + ) + self.cleanup.append(self.virtual_machine) + self.assertEqual( + self.virtual_machine.state, + 'Running', + "VM not in Running state" + ) + content = self.get_vm_content(self.extension.path, self.virtual_machine.instancename) + self.check_vm_content_values(content) + self.virtual_machine.stop(self.apiclient) + self.check_vm_status_from_content(self.extension.path, self.virtual_machine.instancename, 'Stopped') + self.virtual_machine.start(self.apiclient) + self.check_vm_status_from_content(self.extension.path, self.virtual_machine.instancename, 'Running') + self.virtual_machine.reboot(self.apiclient) + self.check_vm_status_from_content(self.extension.path, self.virtual_machine.instancename, 'Running') + self.virtual_machine.delete(self.apiclient, expunge=True) + self.popItemFromCleanup(self.virtual_machine.id) + self.assertFalse( + self.check_vm_content_exist(self.extension.path, self.virtual_machine.instancename), + "VM content exist event after expunge" + ) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_02_run_custom_action(self): + name = 'testaction' + details = [{}] + details[0]['abc'] = 'xyz' + parameters = [{}] + parameter0 = { + 'name': 'Name', + 'type': 'STRING', + 'validationformat': 'NONE', + 'required': True + } + parameters[0] = parameter0 + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name, + enabled=True, + details=details, + parameters=parameters, + successmessage='Successfully completed {{actionName}}' + ) + self.cleanup.append(self.custom_action) + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.compute_offering.id, + networkids=[self.l2_network.id] + ) + self.cleanup.append(self.virtual_machine) + param_val=random_gen() + run_parameters = [{}] + run_parameters[0] = { + 'Name': param_val + } + run_response = self.custom_action.run( + self.apiclient, + resourceid=self.virtual_machine.id, + parameters=run_parameters + ) + self.assertTrue( + run_response.success, + "Action run status not success" + ) + self.assertEquals( + f"Successfully completed {name}", + run_response.result.message, + "Action run status not success" + ) + data = run_response.result.details + self.assertEquals( + f"{param_val} for {self.virtual_machine.instancename}", + run_response.result.details, + "Action response details not match" + ) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_03_run_invalid_custom_action(self): + name = 'invalidaction' + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name, + enabled=True, + errormessage='Failed {{actionName}}' + ) + self.cleanup.append(self.custom_action) + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.compute_offering.id, + networkids=[self.l2_network.id] + ) + self.cleanup.append(self.virtual_machine) + run_response = self.custom_action.run( + self.apiclient, + resourceid=self.virtual_machine.id + ) + self.assertFalse( + run_response.success, + "Action run status not failure" + ) + self.assertEquals( + f"Failed {name}", + run_response.result.message, + "Action run status not success" + ) diff --git a/test/integration/smoke/test_extension_custom_action_lifecycle.py b/test/integration/smoke/test_extension_custom_action_lifecycle.py new file mode 100644 index 000000000000..8e0396a0a3bc --- /dev/null +++ b/test/integration/smoke/test_extension_custom_action_lifecycle.py @@ -0,0 +1,281 @@ +# 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. +""" BVT tests for extension custom actions lifecycle functionalities +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import listManagementServers +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.base import (Extension, + ExtensionCustomAction) +from marvin.lib.common import (get_zone) +from marvin.lib.utils import (random_gen) +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr +# Import System modules +import logging +import random +import string +import time + +_multiprocess_shared_ = True + +class TestExtensions(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestExtensions, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + + cls._cleanup = [] + cls.logger = logging.getLogger('TestExtensions') + cls.logger.setLevel(logging.DEBUG) + + cls.resource_name_suffix = random_gen() + cls.extension = Extension.create( + cls.apiclient, + name=f"ext-{cls.resource_name_suffix}", + type='Orchestrator' + ) + cls._cleanup.append(cls.extension) + + @classmethod + def tearDownClass(cls): + super(TestExtensions, cls).tearDownClass() + + def setUp(self): + self.cleanup = [] + + def tearDown(self): + super(TestExtensions, self).tearDown() + + def popItemFromCleanup(self, item_id): + for idx, x in enumerate(self.cleanup): + if x.id == item_id: + self.cleanup.pop(idx) + break + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_01_extension_create_custom_action(self): + name = random_gen() + details = [{}] + details[0]['abc'] = 'xyz' + parameters = [{}] + parameter0 = { + 'name': 'Name', + 'type': 'STRING', + 'validationformat': 'NONE', + 'required': True + } + parameters[0] = parameter0 + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name, + details=details, + parameters=parameters + ) + self.cleanup.append(self.custom_action) + self.assertEqual( + name, + self.custom_action.name, + "Check custom action name failed" + ) + self.assertEqual( + self.extension.id, + self.custom_action.extensionid, + "Check custom action extension ID failed" + ) + self.assertEqual( + 'VirtualMachine', + self.custom_action.resourcetype, + "Check custom action resorucetype failed" + ) + self.assertFalse( + self.custom_action.enabled, + "Check custom action enabled failed" + ) + self.assertTrue( + self.custom_action.allowedroletypes is not None and len(self.custom_action.allowedroletypes) == 1 and self.custom_action.allowedroletypes[0] == 'Admin', + "Check custom action allowedroletypes failed" + ) + self.assertEqual( + 3, + self.custom_action.timeout, + "Check custom action timeout failed" + ) + action_details = self.custom_action.details.__dict__ + for k, v in details[0].items(): + self.assertIn(k, action_details, f"Key '{k}' should be present in details") + self.assertEqual(v, action_details[k], f"Value for key '{k}' should be '{v}'") + self.assertTrue( + self.custom_action.parameters is not None and len(self.custom_action.parameters) == 1, + "Check custom action parameters count failed" + ) + parameter_result = self.custom_action.parameters[0].__dict__ + for k, v in parameter0.items(): + self.assertIn(k, parameter_result, f"Key '{k}' should be present in the parameter") + self.assertEqual(v, parameter_result[k], f"Parameter property for key '{k}' should be '{v}'") + + actions = self.extension.list_custom_actions(self.apiclient) + self.assertTrue( + actions is not None and len(actions) == 1 and actions[0].id == self.custom_action.id, + "Check extension custom actions failed" + ) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_02_extension_create_custom_action_name_fail(self): + name = random_gen() + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name + ) + try: + self.custom_action1 = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name + ) + self.cleanup.append(self.custom_action1) + self.fail(f"Same name: {name} custom action created twice") + except CloudstackAPIException: pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_03_extension_create_custom_action_resourcetype_fail(self): + try: + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=random_gen(), + resourcetype='Host' + ) + self.cleanup.append(self.custom_action) + self.fail(f"Unknown resourcetype: {type} custom action created") + except CloudstackAPIException: pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_04_extension_update_custom_action(self): + details = [{}] + details[0]['abc'] = 'xyz' + parameters = [{}] + parameter0 = { + 'name': 'Name', + 'type': 'STRING', + 'validationformat': 'NONE', + 'required': True + } + parameters[0] = parameter0 + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=random_gen(), + details=details, + parameters=parameters + ) + self.cleanup.append(self.custom_action) + + details[0]['bca'] = 'yzx' + description = 'Updated test description' + parameter1 = { + 'name': 'COUNT', + 'type': 'NUMBER', + 'validationformat': 'NONE', + 'required': False, + 'valueoptions': '10,100,1000' + } + parameters.append(parameter1) + updated_action = self.custom_action.update( + self.apiclient, + description=description, + enabled=True, + details=details, + parameters=parameters)['extensioncustomaction'] + self.assertTrue( + updated_action.enabled, + "Check custom action enabled failed" + ) + self.assertEqual( + description, + updated_action.description, + "Check custom action description failed" + ) + action_details = updated_action.details.__dict__ + for k, v in details[0].items(): + self.assertIn(k, action_details, f"Key '{k}' should be present in details") + self.assertEqual(v, action_details[k], f"Value for key '{k}' should be '{v}'") + self.assertTrue( + updated_action.parameters is not None and len(updated_action.parameters) == 2, + "Check custom action parameters count failed" + ) + for idx, param in enumerate(parameters): + parameter_result = updated_action.parameters[idx].__dict__ + for k, v in param.items(): + self.assertIn(k, parameter_result, f"Key '{k}' should be present in the parameter") + actual = ','.join(str(x) for x in parameter_result[k]) if k == 'valueoptions' else parameter_result[k] + self.assertEqual(v, actual, f"Parameter property for key '{k}' should be '{v}'") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_05_extension_run_custom_action_invalid_resource_type_fail(self): + name = random_gen() + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name, + enabled=True + ) + self.cleanup.append(self.custom_action) + try: + self.custom_action.run( + self.apiclient, + resourcetype='Host', + resourceid='abcd' + ) + self.fail(f"Invalid resource custom action: {name} ran successfully") + except Exception as e: + msg = str(e) + if msg.startswith("Job failed") == False or "Internal error running action" not in msg == False: + self.fail(f"Unknown exception occurred: {e}") + pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_06_extension_run_custom_action_fail(self): + name = random_gen() + self.custom_action = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + name=name, + enabled=True + ) + self.cleanup.append(self.custom_action) + try: + self.custom_action.run( + self.apiclient, + resourceid='abcd' + ) + self.fail(f"Invalid resource custom action: {name} ran successfully") + except Exception as e: + msg = str(e) + if msg.startswith("Job failed") == False or "Invalid action" not in msg == False: + self.fail(f"Unknown exception occurred: {e}") + pass diff --git a/test/integration/smoke/test_extension_lifecycle.py b/test/integration/smoke/test_extension_lifecycle.py new file mode 100644 index 000000000000..aac380be7df1 --- /dev/null +++ b/test/integration/smoke/test_extension_lifecycle.py @@ -0,0 +1,480 @@ +# 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. +""" BVT tests for extensions lifecycle functionalities +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (listInfrastructure, + listManagementServers) +from marvin.cloudstackException import CloudstackAPIException +from marvin.lib.base import (Extension, + Pod, + Cluster, + Host, + Configurations, + Template, + ServiceOffering, + NetworkOffering, + Network, + VirtualMachine) +from marvin.lib.common import (get_zone) +from marvin.lib.utils import (random_gen) +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr +# Import System modules +import logging +from pathlib import Path +import random +import string +import time + +_multiprocess_shared_ = True + +CONFIG_EXTENSION_PATH_STATE_CHECK_NAME = "extension.path.state.check.interval" + +class TestExtensions(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestExtensions, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + + cls._cleanup = [] + cls.logger = logging.getLogger('TestExtensions') + cls.logger.setLevel(logging.DEBUG) + + @classmethod + def tearDownClass(cls): + super(TestExtensions, cls).tearDownClass() + + def setUp(self): + self.changedConfigurations = {} + self.staticConfigurations = [] + self.cleanup = [] + + def tearDown(self): + restartServer = False + for config in self.changedConfigurations: + value = self.changedConfigurations[config] + logging.info(f"Reverting value of config: {config} to {value}") + Configurations.update(self.apiclient, + config, + value=value) + if config in self.staticConfigurations: + restartServer = True + if restartServer: + self.restartAllManagementServers() + super(TestExtensions, self).tearDown() + + def isManagementUp(self): + try: + self.apiclient.listInfrastructure(listInfrastructure.listInfrastructureCmd()) + return True + except Exception: + return False + + def getManagementServerIps(self): + if self.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + return None + cmd = listManagementServers.listManagementServersCmd() + servers = self.apiclient.listManagementServers(cmd) + active_server_ips = [] + active_server_ips.append(self.mgtSvrDetails["mgtSvrIp"]) + for idx, server in enumerate(servers): + if server.state == 'Up' and server.ipaddress != self.mgtSvrDetails["mgtSvrIp"]: + active_server_ips.append(server.ipaddress) + return active_server_ips + + def restartAllManagementServers(self): + """Restart all management servers + Assumes all servers have same username and password""" + server_ips = self.getManagementServerIps() + if server_ips is None: + self.staticConfigurations.clear() + self.fail(f"MS restarts cannot be done on {self.mgtSvrDetails['mgtSvrIp']}") + return False + logging.info("Restarting all management server") + for idx, server_ip in enumerate(server_ips): + logging.info(f"Restarting management server #{idx} with IP {server_ip}") + sshClient = SshClient( + server_ip, + 22, + self.mgtSvrDetails["user"], + self.mgtSvrDetails["passwd"] + ) + command = "service cloudstack-management stop" + sshClient.execute(command) + command = "service cloudstack-management start" + sshClient.execute(command) + if idx == 0: + # Wait before restarting other management servers to make the first as oldest running + time.sleep(10) + + # Waits for management to come up in 10 mins, when it's up it will continue + timeout = time.time() + (10 * 60) + while time.time() < timeout: + if self.isManagementUp() is True: return True + time.sleep(5) + logging.info("Management server did not come up, failing") + return False + + def changeConfiguration(self, name, value): + current_config = Configurations.list(self.apiclient, name=name)[0] + if current_config.value == str(value): + return + logging.info(f"Current value for config: {name} is {current_config.value}, changing it to {value}") + self.changedConfigurations[name] = current_config.value + if current_config.isdynamic == False: + self.staticConfigurations.append(name) + Configurations.update(self.apiclient, + name, + value=value) + + def create_cluster(self): + pod_list = Pod.list(self.apiclient) + if len(pod_list) <= 0: + self.fail("No Pods found") + pod_id = pod_list[0].id + cluster_services = { + 'clustername': 'C0-Test-' + random_gen(), + 'clustertype': 'CloudManaged' + } + self.cluster = Cluster.create( + self.apiclient, + cluster_services, + self.zone.id, + pod_id, + 'External' + ) + self.cleanup.append(self.cluster) + + def move_extension_path_locally(self, source, target): + try: + Path(source).rename(target) + except Exception as e: + self.fail(f"Failed to move extension path on localhost: {str(e)}") + + def move_extension_path(self, path, restore=False): + logging.info(f"Moving path {path}") + source = path + target = f"{path}-backup" + if restore: + source = target + target = path + server_ips = self.getManagementServerIps() + if server_ips is None: + if self.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + self.move_extension_path_locally(source, target) + return + self.fail(f"Extension path cannot be moved on {cls.mgtSvrDetails['mgtSvrIp']}") + logging.info(f"Moving {path} on all management servers") + command = f"mv {source} {target}" + for idx, server_ip in enumerate(server_ips): + logging.info(f"Moving {path} on management server #{idx} with IP {server_ip}") + sshClient = SshClient( + server_ip, + 22, + self.mgtSvrDetails["user"], + self.mgtSvrDetails["passwd"] + ) + sshClient.execute(command) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_01_create_extension(self): + name = random_gen() + type = 'Orchestrator' + details = [{}] + details[0]['abc'] = 'xyz' + self.extension = Extension.create( + self.apiclient, + name=name, + type=type, + details=details + ) + self.cleanup.append(self.extension) + self.assertEqual( + name, + self.extension.name, + "Check extension name failed" + ) + self.assertEqual( + type, + self.extension.type, + "Check extension type failed" + ) + self.assertEqual( + 'Enabled', + self.extension.state, + "Check extension state failed" + ) + self.assertTrue( + self.extension.pathready, + "Check extension path ready failed" + ) + extension_details = self.extension.details.__dict__ + for k, v in details[0].items(): + self.assertIn(k, extension_details, f"Key '{k}' should be present in details") + self.assertEqual(v, extension_details[k], f"Value for key '{k}' should be '{v}'") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_02_create_extension_type_fail(self): + type = 'Random' + try: + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type=type + ) + self.cleanup.append(self.extension) + self.fail(f"Unknown type: {type} extension created") + except CloudstackAPIException: pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_03_create_extension_name_fail(self): + name = random_gen() + self.extension = Extension.create( + self.apiclient, + name=name, + type='Orchestrator' + ) + self.cleanup.append(self.extension) + try: + self.extension1 = Extension.create( + self.apiclient, + name=name, + type='Orchestrator' + ) + self.cleanup.append(self.extension1) + self.fail(f"Same name: {name} extension created twice") + except CloudstackAPIException: pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_04_update_extension(self): + details = [{}] + details[0]['abc'] = 'xyz' + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator', + description='Test description', + state='Disabled', + details=details + ) + self.cleanup.append(self.extension) + + details[0]['bca'] = 'yzx' + description = 'Updated test description' + state = 'Enabled' + updated_extension = self.extension.update( + self.apiclient, + description=description, + state=state, + details=details) + self.assertEqual( + description, + updated_extension.description, + "Check extension description" + ) + self.assertEqual( + state, + updated_extension.state, + "Check extension state" + ) + self.assertTrue( + updated_extension.details is not None, + "Check extension details not None" + ) + updated_details = updated_extension.details.__dict__ + for k, v in details[0].items(): + self.assertIn(k, updated_details, f"Key '{k}' should be present in updated details") + self.assertEqual(v, updated_details[k], f"Value for key '{k}' should be '{v}'") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_05_register_unregister_extension(self): + self.create_cluster() + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator' + ) + self.cleanup.append(self.extension) + registered_extension = self.extension.register(self.apiclient, self.cluster.id, 'Cluster') + resource_found = False + if registered_extension is not None and registered_extension.resources is not None and len(registered_extension.resources) > 0: + for resource in registered_extension.resources: + if resource.id == self.cluster.id and resource.type == 'Cluster': + resource_found = True + break + self.assertTrue(resource_found, "Registered resource not found") + self.extension.unregister(self.apiclient, self.cluster.id, 'Cluster') + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_06_register_extension_already_fail(self): + self.create_cluster() + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator' + ) + self.cleanup.append(self.extension) + registered_extension = self.extension.register(self.apiclient,self.cluster.id, 'Cluster')['extension'] + self.extension1 = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator' + ) + self.cleanup.append(self.extension1) + try: + self.extension1.register(self.apiclient, self.cluster.id, 'Cluster') + self.fail(f"Same cluster: {cluster.id} registered with two extensions") + except CloudstackAPIException: pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_07_delete_extension_registered_resource_fail(self): + self.create_cluster() + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator' + ) + self.cleanup.append(self.extension) + self.extension.register(self.apiclient, self.cluster.id, 'Cluster') + try: + self.extension.delete(self.apiclient, False) + self.fail("Deleted extensions which is registered to a resource") + except CloudstackAPIException: pass + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_08_extension_sync(self): + sync_task_interval = 60 + if self.mgtSvrDetails["mgtSvrIp"] in ('localhost', '127.0.0.1'): + sync_config = Configurations.list(self.apiclient, name=CONFIG_EXTENSION_PATH_STATE_CHECK_NAME)[0] + if sync_config.value != str(sync_task_interval): + self.skipTest("Test running on localhost, MS cannot be restarted to change config") + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator' + ) + self.assertTrue(self.extension.pathready, "Path not ready") + self.changeConfiguration(CONFIG_EXTENSION_PATH_STATE_CHECK_NAME, sync_task_interval) + if len(self.staticConfigurations) > 0: + self.restartAllManagementServers() + self.move_extension_path(self.extension.path) + wait_multiple = 1.5 + wait = wait_multiple * sync_task_interval + logging.info(f"Waiting for {wait_multiple}x{sync_task_interval} = {wait} seconds for background task to execute") + time.sleep(wait) + ext = Extension.list( + self.apiclient, + id=self.extension.id + )[0] + self.assertFalse(ext.pathready, "Path is still ready") + self.move_extension_path(self.extension.path, True) + logging.info(f"Waiting for {wait_multiple}x{sync_task_interval} = {wait} seconds for background task to execute") + time.sleep(wait) + ext = Extension.list( + self.apiclient, + id=self.extension.id + )[0] + self.assertTrue(ext.pathready, "Path not ready") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], required_hardware="false") + def test_09_extension_deploy_vm(self): + self.create_cluster() + self.extension = Extension.create( + self.apiclient, + name=random_gen(), + type='Orchestrator' + ) + self.cleanup.append(self.extension) + self.extension.register(self.apiclient, self.cluster.id, 'Cluster') + details = { + 'url': random_gen(), + 'zoneid': self.cluster.zoneid, + 'podid': self.cluster.podid, + 'username': 'External', + 'password': 'External' + } + self.host = Host.create( + self.apiclient, + self.cluster, + details, + hypervisor=self.cluster.hypervisortype + ) + self.cleanup.append(self.host) + template_name = "ext-" + random_gen() + template_data = { + "name": template_name, + "displaytext": template_name, + "format": self.host.hypervisor, + "hypervisor": self.host.hypervisor, + "ostype": "Other Linux (64-bit)", + "url": template_name, + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "extensionid": self.extension.id + } + self.template = Template.register( + self.apiclient, + template_data, + zoneid=self.zone.id, + hypervisor=self.cluster.hypervisortype + ) + self.cleanup.append(self.template) + logging.info("Waiting for 3 seconds for template to be ready") + time.sleep(3) + self.compute_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["tiny"]) + self.cleanup.append(self.compute_offering) + self.network_offering = NetworkOffering.create( + self.apiclient, + self.services["l2-network_offering"], + ) + self.cleanup.append(self.network_offering) + self.network_offering.update(self.apiclient, state='Enabled') + self.services["network"]["networkoffering"] = self.network_offering.id + self.l2_network = Network.create( + self.apiclient, + self.services["l2-network"], + zoneid=self.zone.id, + networkofferingid=self.network_offering.id + ) + self.cleanup.append(self.l2_network) + self.services["virtual_machine"]["zoneid"] = self.zone.id + self.services["virtual_machine"]["template"] = self.template.id + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.compute_offering.id, + networkids=[self.l2_network.id] + ) + self.cleanup.append(self.virtual_machine) + self.assertEqual( + self.virtual_machine.state, + 'Running', + "VM not in Running state" + ) diff --git a/test/integration/smoke/test_human_readable_logs.py b/test/integration/smoke/test_human_readable_logs.py index fb972511f9c1..c2478bfe5766 100644 --- a/test/integration/smoke/test_human_readable_logs.py +++ b/test/integration/smoke/test_human_readable_logs.py @@ -48,9 +48,9 @@ def test_01_disableHumanReadableLogs(self): sshClient.execute(command) # CapacityChecker runs as soon as management server is up - # Check if "usedMem: (" is printed out within 60 seconds while server is starting - command = "timeout 60 tail -f /var/log/cloudstack/management/management-server.log | grep 'usedMem: ('" - sshClient.timeout = 60 + # Check if "usedMem: (" is printed out within 120 seconds while server is starting + command = "timeout 120 tail -f /var/log/cloudstack/management/management-server.log | grep 'usedMem: ('" + sshClient.timeout = 120 result = sshClient.runCommand(command) self.assertTrue(result['status'] == "FAILED") @@ -70,7 +70,7 @@ def test_02_enableHumanReadableLogs(self): sshClient.execute(command) # CapacityChecker runs as soon as management server is up - # Check if "usedMem: (" is printed out within 60 seconds while server is restarting + # Check if "usedMem: (" is printed out within 120 seconds while server is restarting command = "timeout 120 tail -f /var/log/cloudstack/management/management-server.log | grep 'usedMem: ('" sshClient.timeout = 120 result = sshClient.runCommand(command) diff --git a/test/integration/smoke/test_kms_lifecycle.py b/test/integration/smoke/test_kms_lifecycle.py new file mode 100644 index 000000000000..79deb5bdf0e2 --- /dev/null +++ b/test/integration/smoke/test_kms_lifecycle.py @@ -0,0 +1,499 @@ +# 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. + +""" +Lifecycle integration tests for the KMS (Key Management Service) feature. + +Covers: + - HSM profile CRUD (add / list / update / delete) + - KMS key CRUD (create / list / update / delete) + - Key rotation + - Multi-tenancy / access isolation + - Negative scenarios (delete key in use, duplicate name, delete profile with keys) + +All tests use the built-in *database* KMS provider so that they can run in any +CI environment without real HSM hardware. +""" + +import random +import string + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import ( + Account, + Domain, + HSMProfile, + KMSKey, + VirtualMachine, + ServiceOffering, + Volume, +) +from marvin.lib.common import get_zone, get_domain, get_template +from marvin.lib.utils import cleanup_resources +from nose.plugins.attrib import attr + +def _random_name(prefix="test-kms"): + suffix = "".join(random.choices(string.ascii_lowercase + string.digits, k=6)) + return f"{prefix}-{suffix}" + + +class TestKMSLifecycle(cloudstackTestCase): + """ + End-to-end lifecycle tests for KMS keys and HSM profiles. + + Each test gets a fresh Domain + Account pair via setUp so that + domain-level isolation is guaranteed and cleanup is straightforward. + """ + + @classmethod + def setUpClass(cls): + cls.test_client = super(TestKMSLifecycle, cls).getClsTestClient() + cls.apiclient = cls.test_client.getApiClient() + cls.zone = get_zone(cls.apiclient, cls.test_client.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + + cls._cleanup = [] + + # The built-in database provider is seeded as a public, system-owned profile named + # "HSM Database Provider". listHSMProfiles has no server-side name filter, and a root + # admin's listing without listall is restricted to admin-owned profiles, so we must + # pass listall=True and match the name client-side. + cls.default_profile = cls._find_default_profile(cls.apiclient) + if cls.default_profile and not cls.default_profile.enabled: + hsm = HSMProfile({"id": cls.default_profile.id}) + hsm.update(cls.apiclient, enabled=True) + # Re-fetch to get updated state + cls.default_profile = cls._find_default_profile(cls.apiclient) + + @staticmethod + def _find_default_profile(apiclient): + """Locate the seeded public 'HSM Database Provider' profile.""" + profiles = HSMProfile.list( + apiclient, listall=True, keyword="HSM Database Provider" + ) or [] + for profile in profiles: + if profile.name == "HSM Database Provider": + return profile + return None + + + @classmethod + def tearDownClass(cls): + super(TestKMSLifecycle, cls).tearDownClass() + + # ------------------------------------------------------------------ + # Per-test helpers + # ------------------------------------------------------------------ + def setUp(self): + self.apiclient = self.test_client.getApiClient() + self.cleanup = [] + self._create_domain_and_account() + + def tearDown(self): + self.cleanup.reverse() + cleanup_resources(self.apiclient, self.cleanup) + + def _create_domain_and_account(self, is_domain_admin=False): + """Create a child domain + account and register them for cleanup.""" + self.child_domain = Domain.create( + self.apiclient, + {"name": _random_name("kms-dom")}, + parentdomainid=self.domain.id, + ) + self.cleanup.append(self.child_domain) + + self.user_account = Account.create( + self.apiclient, + { + "firstname": "KMS", + "lastname": "Test", + "email": "kmstest@example.com", + "username": _random_name("kmsuser"), + "password": "password", + }, + domainid=self.child_domain.id, + admin=is_domain_admin, + ) + self.cleanup.append(self.user_account) + + # API client scoped to the new user + self.user_apiclient = self.test_client.getUserApiClient( + UserName=self.user_account.name, + DomainName=self.child_domain.name, + ) + + def _create_kms_key(self, name, profile_id, apiclient=None, zoneid=None, purpose=None): + api_client = apiclient or self.apiclient + zone_id = zoneid or self.zone.id + key = KMSKey.create( + api_client, + name=name, + zoneid=zone_id, + hsmprofileid=profile_id, + purpose=purpose + ) + self.cleanup.append(key) + return key + + def _create_hsm_profile(self, name, protocol="database", zoneid=None): + zone_id = zoneid or self.zone.id + profile = HSMProfile.create( + self.apiclient, + name=name, + protocol=protocol, + zoneid=zone_id, + ) + self.cleanup.append(profile) + return profile + + + # ================================================================== + # HSM Profile lifecycle tests (01 – 03, 11, 13) + # ================================================================== + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_01_add_hsm_profile_admin(self): + """Test: admin creates a system-wide database HSM profile.""" + profile_name = _random_name("hsm-prof") + profile = self._create_hsm_profile(name=profile_name) + self.assertIsNotNone(profile, "HSM profile creation returned None") + + self.assertEqual( + profile.name, profile_name, + "HSM profile name does not match the requested name" + ) + self.assertEqual( + profile.protocol.lower(), "database", + "HSM profile protocol should be 'database'" + ) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_02_list_hsm_profiles(self): + """Test: list HSM profiles and verify a created profile is present.""" + profile_name = _random_name("hsm-list") + profile = self._create_hsm_profile(name=profile_name) + + profiles = HSMProfile.list(self.apiclient, id=profile.id) + self.assertIsNotNone(profiles, "listHSMProfiles returned None") + self.assertEqual(len(profiles), 1, "Expected exactly one HSM profile matching the ID") + self.assertEqual(profiles[0].id, profile.id, "Profile IDs do not match") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_03_update_hsm_profile(self): + """Test: update the name of an existing HSM profile.""" + profile = self._create_hsm_profile(name=_random_name("hsm-upd")) + + new_name = _random_name("hsm-renamed") + updated = profile.update(self.apiclient, name=new_name) + + self.assertIsNotNone(updated, "updateHSMProfile returned None") + self.assertEqual( + updated.name, new_name, + "Profile name was not updated" + ) + + # ================================================================== + # KMS Key CRUD tests (tests 04 – 09) + # ================================================================== + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_04_create_kms_key_admin(self): + """Test: admin creates a KMS key in the zone, verifies fields.""" + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + key_name = _random_name("kms-key") + key = self._create_kms_key(name=key_name, profile_id=profile.id, purpose="volume") + self.assertIsNotNone(key, "createKMSKey returned None") + + self.assertEqual(key.name, key_name, "Key name does not match") + self.assertEqual( + key.zoneid, self.zone.id, + "Key zone ID does not match the requested zone" + ) + self.assertTrue(key.enabled, "Newly created key should be enabled") + self.assertIsNotNone(key.id, "Key UUID should not be None") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_05_list_kms_keys(self): + """Test: list KMS keys filtered by zone and by id.""" + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + key = self._create_kms_key(name=_random_name("key"), profile_id=profile.id) + + # Filter by explicit key ID + keys_by_id = KMSKey.list(self.apiclient, id=key.id) + self.assertIsNotNone(keys_by_id, "listKMSKeys by id returned None") + self.assertEqual(len(keys_by_id), 1, "Expected exactly one key matching given ID") + self.assertEqual(keys_by_id[0].id, key.id, "Key IDs do not match") + + # Filter by zone + keys_by_zone = KMSKey.list(self.apiclient, zoneid=self.zone.id) + self.assertIsNotNone(keys_by_zone, "listKMSKeys by zone returned None") + found = any(k.id == key.id for k in keys_by_zone) + self.assertTrue(found, "Newly created key not found when listing by zone") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_06_update_kms_key(self): + """Test: update key name, description, and enabled status.""" + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + key = self._create_kms_key(name=_random_name("key-upd"), profile_id=profile.id) + + new_name = _random_name("key-renamed") + new_desc = "Updated description" + updated = key.update( + self.apiclient, + name=new_name, + description=new_desc, + enabled=False, + ) + + self.assertIsNotNone(updated, "updateKMSKey returned None") + self.assertEqual(updated.name, new_name, "Key name was not updated") + self.assertEqual(updated.description, new_desc, "Key description was not updated") + self.assertFalse(updated.enabled, "Key should be disabled after update") + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_07_create_kms_key_user(self): + """Test: domain user creates their own KMS key; verifies ownership.""" + # Admin creates the system HSM profile first + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + key_name = _random_name("user-key") + key = self._create_kms_key(name=key_name, profile_id=profile.id, apiclient=self.user_apiclient) + self.assertIsNotNone(key, "User-level createKMSKey returned None") + + self.assertEqual(key.name, key_name, "Key name does not match") + self.assertEqual( + key.account, self.user_account.name, + "Key account should belong to the creating user account" + ) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_08_list_kms_keys_user_isolation(self): + """Test: User A's keys are NOT visible to User B.""" + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + # User A key (self.user_account) + key_a = self._create_kms_key(name=_random_name("key-a"), profile_id=profile.id, apiclient=self.user_apiclient) + + # Create User B in a separate child domain + domain_b = Domain.create( + self.apiclient, + {"name": _random_name("dom-b")}, + parentdomainid=self.domain.id, + ) + self.cleanup.append(domain_b) + account_b = Account.create( + self.apiclient, + { + "firstname": "UserB", + "lastname": "Test", + "email": "userb@example.com", + "username": _random_name("userb"), + "password": "password", + }, + domainid=domain_b.id, + admin=False, + ) + self.cleanup.append(account_b) + apiclient_b = self.test_client.getUserApiClient( + UserName=account_b.name, + DomainName=domain_b.name, + ) + + # User B should not be able to see User A's key + keys_for_b = KMSKey.list(apiclient_b, id=key_a.id) + if keys_for_b: + self.assertNotEqual( + keys_for_b[0].accountid, self.user_account.id, + "User B should not see User A's KMS keys" + ) + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_09_delete_kms_key(self): + """Test: delete a KMS key that is not in use; verify it is gone.""" + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + key = self._create_kms_key(name=_random_name("key-del"), profile_id=profile.id) + + key.delete(self.apiclient) + self.cleanup.remove(key) + + # Verify the key no longer appears in listings + keys = KMSKey.list(self.apiclient, id=key.id) + self.assertTrue( + not keys, + "Deleted KMS key should not appear in listKMSKeys" + ) + + # ================================================================== + # Key rotation (test 10) + # ================================================================== + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_10_rotate_kms_key(self): + """Test: rotate a KMS key; verify the key version increments.""" + if not self.default_profile: + self.skipTest("Default HSM profile 'default' not found") + profile = self.default_profile + + key = self._create_kms_key(name=_random_name("key-rot"), profile_id=profile.id) + + initial_version = key.version + + key.rotate(self.apiclient) + + # Fetch the updated key details and confirm version incremented + keys = KMSKey.list(self.apiclient, id=key.id) + self.assertIsNotNone(keys, "listKMSKeys after rotation returned None") + self.assertEqual(len(keys), 1, "Expected exactly one key after rotation") + rotated_key = keys[0] + + self.assertGreater( + rotated_key.version, + initial_version, + f"Key version should increase after rotation (was {initial_version}, " + f"got {rotated_key.version})" + ) + + # ================================================================== + # Negative tests (11) + # ================================================================== + + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_11_delete_hsm_profile_with_keys_negative(self): + """ + Negative test: deleting an HSM profile that still has associated KMS + keys should be rejected. + """ + profile = self._create_hsm_profile(name=_random_name("hsm-with-key")) + + key = self._create_kms_key(name=_random_name("key-blocks-del"), profile_id=profile.id) + + with self.assertRaises(Exception, + msg="Deleting HSM profile with active keys should fail"): + profile.delete(self.apiclient) + + # ================================================================== + # VM Encryption tests (12) + # ================================================================== + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_12_deploy_vm_with_root_disk_encryption(self): + """ + Test: deploy a VM with its root disk encrypted using a KMS key. + Verify that the VM starts and the root volume has the KMS key ID. + """ + # 1. Create a KMS key for the user + key = self._create_kms_key(name=_random_name("vm-root-key"), profile_id=self.default_profile.id, apiclient=self.user_apiclient) + + # 2. Get a template and a service offering + template = get_template( + self.apiclient, + self.zone.id, + self.test_client.getParsedTestDataConfig().get("ostype", "CentOS 7.0 (64-bit)") + ) + if template == -1 or template is None: + self.fail("Check for template failed") + + service_offering = ServiceOffering.create( + self.apiclient, + self.test_client.getParsedTestDataConfig()["service_offering"] + ) + self.cleanup.append(service_offering) + + # 3. Deploy VM with root disk encryption + vm = VirtualMachine.create( + self.user_apiclient, + self.test_client.getParsedTestDataConfig()["virtual_machine"], + templateid=template.id, + accountid=self.user_account.name, + domainid=self.child_domain.id, + serviceofferingid=service_offering.id, + zoneid=self.zone.id, + rootdiskkmskeyid=key.id + ) + self.cleanup.append(vm) + + self.assertEqual( + vm.state, + "Running", + "VM should be in Running state after deployment" + ) + + # 4. Verify the root volume has the KMS key ID + volumes = Volume.list( + self.user_apiclient, + virtualmachineid=vm.id, + type='ROOT', + listall=True + ) + self.assertTrue( + volumes and len(volumes) > 0, + "VM should have at least one ROOT volume" + ) + root_volume = volumes[0] + self.assertEqual( + str(root_volume.kmskeyid), + str(key.id), + f"Root volume should have KMS key ID {key.id}, found {root_volume.kmskeyid}" + ) + + # ================================================================== + # HSM Profile cleanup (13) + # ================================================================== + + @attr(tags=["devcloud", "advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="false") + def test_13_delete_hsm_profile(self): + """Test: delete an HSM profile that has no associated keys; verify it is gone.""" + profile = self._create_hsm_profile(name=_random_name("hsm-gone")) + + profile.delete(self.apiclient) + self.cleanup.remove(profile) + + profiles = HSMProfile.list(self.apiclient, id=profile.id) + self.assertTrue( + not profiles, + "Deleted HSM profile should not appear in listHSMProfiles" + ) diff --git a/test/integration/smoke/test_kubernetes_clusters.py b/test/integration/smoke/test_kubernetes_clusters.py index 20f1cb3224ae..a77829bc255b 100644 --- a/test/integration/smoke/test_kubernetes_clusters.py +++ b/test/integration/smoke/test_kubernetes_clusters.py @@ -35,7 +35,9 @@ destroyVirtualMachine, deleteNetwork, addVirtualMachinesToKubernetesCluster, - removeVirtualMachinesFromKubernetesCluster) + removeVirtualMachinesFromKubernetesCluster, + addNodesToKubernetesCluster, + removeNodesFromKubernetesCluster) from marvin.cloudstackException import CloudstackAPIException from marvin.codes import PASS, FAILED from marvin.lib.base import (Template, @@ -49,28 +51,84 @@ VPC, NetworkACLList, NetworkACL, - VirtualMachine) + VirtualMachine, + PublicIPAddress, + FireWallRule, + NATRule) from marvin.lib.utils import (cleanup_resources, validateList, random_gen) from marvin.lib.common import (get_zone, get_domain, - get_template) + get_template, + get_test_template) from marvin.sshClient import SshClient from nose.plugins.attrib import attr from marvin.lib.decoratorGenerators import skipTestIf from kubernetes import client, config -import time, io, yaml +import time, io, yaml, random _multiprocess_shared_ = True k8s_cluster = None +k8s_cluster_node_offerings = None VPC_DATA = { "cidr": "10.1.0.0/22", "tier1_gateway": "10.1.1.1", "tier_netmask": "255.255.255.0" } +RAND_SUFFIX = random_gen() +NODES_TEMPLATE = { + "kvm": { + "name": "cks-u2204-kvm-" + RAND_SUFFIX, + "displaytext": "cks-u2204-kvm-" + RAND_SUFFIX, + "format": "qcow2", + "hypervisor": "kvm", + "ostype": "Ubuntu 22.04 LTS", + "url": "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-kvm.qcow2.bz2", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "forcks": "True" + }, + "xenserver": { + "name": "cks-u2204-hyperv-" + RAND_SUFFIX, + "displaytext": "cks-u2204-hyperv-" + RAND_SUFFIX, + "format": "vhd", + "hypervisor": "xenserver", + "ostype": "Ubuntu 22.04 LTS", + "url": "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-hyperv.vhd.zip", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "forcks": "True" + }, + "hyperv": { + "name": "cks-u2204-hyperv-" + RAND_SUFFIX, + "displaytext": "cks-u2204-hyperv-" + RAND_SUFFIX, + "format": "vhd", + "hypervisor": "hyperv", + "ostype": "Ubuntu 22.04 LTS", + "url": "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-hyperv.vhd.zip", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "forcks": "True" + }, + "vmware": { + "name": "cks-u2204-vmware-" + RAND_SUFFIX, + "displaytext": "cks-u2204-vmware-" + RAND_SUFFIX, + "format": "ova", + "hypervisor": "vmware", + "ostype": "Ubuntu 22.04 LTS", + "url": "https://download.cloudstack.org/testing/custom_templates/ubuntu/22.04/cks-ubuntu-2204-vmware.ova", + "requireshvm": "True", + "ispublic": "True", + "isextractable": "True", + "forcks": "True" + } +} class TestKubernetesCluster(cloudstackTestCase): @@ -84,6 +142,7 @@ def setUpClass(cls): cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ cls.hypervisorNotSupported = False + cls.hypervisorIsNotVmware = cls.hypervisor.lower() != "vmware" if cls.hypervisor.lower() not in ["kvm", "vmware", "xenserver"]: cls.hypervisorNotSupported = True cls.setup_failed = False @@ -129,13 +188,40 @@ def setUpClass(cls): (cls.services["cks_kubernetes_versions"][cls.k8s_version_to]["semanticversion"], cls.services["cks_kubernetes_versions"][cls.k8s_version_to]["url"], e)) if cls.setup_failed == False: + cls.nodes_template = None + cls.mgmtSshKey = None + if cls.hypervisor.lower() == "vmware": + cls.nodes_template = get_test_template(cls.apiclient, + cls.zone.id, + cls.hypervisor, + NODES_TEMPLATE) + cls.nodes_template.update(cls.apiclient, forcks=True) + cls.mgmtSshKey = cls.getMgmtSshKey() cks_offering_data = cls.services["cks_service_offering"] cks_offering_data["name"] = 'CKS-Instance-' + random_gen() cls.cks_service_offering = ServiceOffering.create( cls.apiclient, cks_offering_data ) + cks_offering_data["name"] = 'CKS-Worker-Offering-' + random_gen() + cls.cks_worker_nodes_offering = ServiceOffering.create( + cls.apiclient, + cks_offering_data + ) + cks_offering_data["name"] = 'CKS-Control-Offering-' + random_gen() + cls.cks_control_nodes_offering = ServiceOffering.create( + cls.apiclient, + cks_offering_data + ) + cks_offering_data["name"] = 'CKS-Etcd-Offering-' + random_gen() + cls.cks_etcd_nodes_offering = ServiceOffering.create( + cls.apiclient, + cks_offering_data + ) cls._cleanup.append(cls.cks_service_offering) + cls._cleanup.append(cls.cks_worker_nodes_offering) + cls._cleanup.append(cls.cks_control_nodes_offering) + cls._cleanup.append(cls.cks_etcd_nodes_offering) cls.domain = get_domain(cls.apiclient) cls.account = Account.create( cls.apiclient, @@ -204,6 +290,19 @@ def updateVmwareSettings(cls, tearDown): name="vmware.create.full.clone", value=value) + @classmethod + def getMgmtSshKey(cls): + """Get the management server SSH public key""" + sshClient = SshClient( + cls.mgtSvrDetails["mgtSvrIp"], + 22, + cls.mgtSvrDetails["user"], + cls.mgtSvrDetails["passwd"] + ) + command = "cat /var/cloudstack/management/.ssh/id_rsa.pub" + response = sshClient.execute(command) + return str(response[0]) + @classmethod def restartServer(cls): """Restart management server""" @@ -644,6 +743,151 @@ def test_11_test_unmanaged_cluster_lifecycle(self): self.deleteKubernetesClusterAndVerify(cluster.id) return + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_12_test_deploy_cluster_different_offerings_per_node_type(self): + """Test creating a CKS cluster with different offerings per node type + + # Validate the following on Kubernetes cluster creation: + # - Use a service offering for control nodes + # - Use a service offering for worker nodes + """ + if self.setup_failed == True: + self.fail("Setup incomplete") + cluster = self.getValidKubernetesCluster(worker_offering=self.cks_worker_nodes_offering, + control_offering=self.cks_control_nodes_offering) + self.assertEqual( + cluster.workerofferingid, + self.cks_worker_nodes_offering.id, + "Check Worker Nodes Offering {}, {}".format(cluster.workerofferingid, self.cks_worker_nodes_offering.id) + ) + self.assertEqual( + cluster.controlofferingid, + self.cks_control_nodes_offering.id, + "Check Control Nodes Offering {}, {}".format(cluster.workerofferingid, self.cks_worker_nodes_offering.id) + ) + self.assertEqual( + cluster.etcdnodes, + 0, + "No Etcd Nodes expected but got {}".format(cluster.etcdnodes) + ) + self.debug("Deleting Kubernetes cluster with ID: %s" % cluster.id) + self.deleteKubernetesClusterAndVerify(cluster.id) + return + + @attr(tags=["advanced", "smoke"], required_hardware="true") + @skipTestIf("hypervisorIsNotVmware") + def test_13_test_add_external_nodes_to_cluster(self): + """Test adding and removing external nodes to CKS clusters + + # Validate the following: + # - Deploy Kubernetes Cluster + # - Deploy VM on the same network as the Kubernetes cluster with the worker nodes offering and CKS ready template + # - Add external node to the Kubernetes Cluster + # - Remove external node from the Kubernetes Cluster + """ + if self.setup_failed == True: + self.fail("Setup incomplete") + cluster = self.getValidKubernetesCluster(worker_offering=self.cks_worker_nodes_offering, + control_offering=self.cks_control_nodes_offering) + self.assertEqual( + cluster.size, + 1, + "Expected 1 worker node but got {}".format(cluster.size) + ) + self.services["virtual_machine"]["template"] = self.nodes_template.id + external_node = VirtualMachine.create(self.apiclient, + self.services["virtual_machine"], + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + rootdiskcontroller="osdefault", + rootdisksize=8, + serviceofferingid=self.cks_worker_nodes_offering.id, + networkids=cluster.networkid) + + # Acquire public IP and create Port Forwarding Rule and Firewall rule for SSH access + free_ip_addresses = PublicIPAddress.list( + self.apiclient, + domainid=self.account.domainid, + account=self.account.name, + forvirtualnetwork=True, + state='Free' + ) + random.shuffle(free_ip_addresses) + external_node_ip = free_ip_addresses[0] + external_node_ipaddress = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + networkid=cluster.networkid, + ipaddress=external_node_ip.ipaddress + ) + self.debug("Creating Firewall rule for VM ID: %s" % external_node.id) + fw_rule = FireWallRule.create( + self.apiclient, + ipaddressid=external_node_ip.id, + protocol='TCP', + cidrlist=['0.0.0.0/0'], + startport=22, + endport=22 + ) + pf_rule = { + "privateport": 22, + "publicport": 22, + "protocol": "TCP" + } + nat_rule = NATRule.create( + self.apiclient, + external_node, + pf_rule, + ipaddressid=external_node_ip.id + ) + + # Add the management server SSH key to the authorized hosts on the external node + node_ssh_client = SshClient( + external_node_ip.ipaddress, + 22, + 'cloud', + 'cloud', + retries=30, + delay=10 + ) + node_ssh_client.execute("echo '" + self.mgmtSshKey + "' > ~/.ssh/authorized_keys") + # Remove acquired public IP address and rules + nat_rule.delete(self.apiclient) + fw_rule.delete(self.apiclient) + external_node_ipaddress.delete(self.apiclient) + + self.addExternalNodesToKubernetesCluster(cluster.id, [external_node.id]) + cluster = self.listKubernetesCluster(cluster.id) + self.assertEqual( + cluster.size, + 2, + "Expected 2 worker nodes but got {}".format(cluster.size) + ) + self.removeExternalNodesFromKubernetesCluster(cluster.id, [external_node.id]) + cluster = self.listKubernetesCluster(cluster.id) + self.assertEqual( + cluster.size, + 1, + "Expected 1 worker node but got {}".format(cluster.size) + ) + VirtualMachine.delete(external_node, self.apiclient, expunge=True) + self.debug("Deleting Kubernetes cluster with ID: %s" % cluster.id) + self.deleteKubernetesClusterAndVerify(cluster.id) + return + + def addExternalNodesToKubernetesCluster(self, cluster_id, vm_list): + cmd = addNodesToKubernetesCluster.addNodesToKubernetesClusterCmd() + cmd.id = cluster_id + cmd.nodeids = vm_list + return self.apiclient.addNodesToKubernetesCluster(cmd) + + def removeExternalNodesFromKubernetesCluster(self, cluster_id, vm_list): + cmd = removeNodesFromKubernetesCluster.removeNodesFromKubernetesClusterCmd() + cmd.id = cluster_id + cmd.nodeids = vm_list + return self.apiclient.removeNodesFromKubernetesCluster(cmd) + def addVirtualMachinesToKubernetesCluster(self, cluster_id, vm_list): cmd = addVirtualMachinesToKubernetesCluster.addVirtualMachinesToKubernetesClusterCmd() cmd.id = cluster_id @@ -658,8 +902,8 @@ def removeVirtualMachinesFromKubernetesCluster(self, cluster_id, vm_list): return self.apiclient.removeVirtualMachinesFromKubernetesCluster(cmd) - - def createKubernetesCluster(self, name, version_id, size=1, control_nodes=1, cluster_type='CloudManaged'): + def createKubernetesCluster(self, name, version_id, size=1, control_nodes=1, etcd_nodes=0, cluster_type='CloudManaged', + workers_offering=None, control_offering=None, etcd_offering=None): createKubernetesClusterCmd = createKubernetesCluster.createKubernetesClusterCmd() createKubernetesClusterCmd.name = name createKubernetesClusterCmd.description = name + "-description" @@ -672,6 +916,22 @@ def createKubernetesCluster(self, name, version_id, size=1, control_nodes=1, clu createKubernetesClusterCmd.account = self.account.name createKubernetesClusterCmd.domainid = self.domain.id createKubernetesClusterCmd.clustertype = cluster_type + if workers_offering: + createKubernetesClusterCmd.nodeofferings.append({ + "node": "WORKER", + "offering": workers_offering.id + }) + if control_offering: + createKubernetesClusterCmd.nodeofferings.append({ + "node": "CONTROL", + "offering": control_offering.id + }) + if etcd_nodes > 0 and etcd_offering: + createKubernetesClusterCmd.etcdnodes = etcd_nodes + createKubernetesClusterCmd.nodeofferings.append({ + "node": "ETCD", + "offering": etcd_offering.id + }) if self.default_network: createKubernetesClusterCmd.networkid = self.default_network.id clusterResponse = self.apiclient.createKubernetesCluster(createKubernetesClusterCmd) @@ -735,7 +995,8 @@ def waitForAutoscalerPodInRunningState(self, cluster_id, retries=5, interval=60) retries = retries - 1 return False - def getValidKubernetesCluster(self, size=1, control_nodes=1, version={}): + def getValidKubernetesCluster(self, size=1, control_nodes=1, version={}, etcd_nodes=0, + worker_offering=None, control_offering=None, etcd_offering=None): cluster = k8s_cluster # Does a cluster already exist ? @@ -743,7 +1004,9 @@ def getValidKubernetesCluster(self, size=1, control_nodes=1, version={}): if not version: version = self.kubernetes_version_v2 self.debug("No existing cluster available, k8s_cluster: %s" % cluster) - return self.createNewKubernetesCluster(version, size, control_nodes) + return self.createNewKubernetesCluster(version, size, control_nodes, etcd_nodes=etcd_nodes, + worker_offering=worker_offering, control_offering=control_offering, + etcd_offering=etcd_offering) # Is the existing cluster what is needed ? valid = cluster.size == size and cluster.controlnodes == control_nodes @@ -759,7 +1022,9 @@ def getValidKubernetesCluster(self, size=1, control_nodes=1, version={}): if cluster == None: # Looks like the cluster disappeared ! self.debug("Existing cluster, k8s_cluster ID: %s not returned by list API" % cluster_id) - return self.createNewKubernetesCluster(version, size, control_nodes) + return self.createNewKubernetesCluster(version, size, control_nodes, etcd_nodes=etcd_nodes, + worker_offering=worker_offering, control_offering=control_offering, + etcd_offering=etcd_offering) if valid: try: @@ -775,13 +1040,18 @@ def getValidKubernetesCluster(self, size=1, control_nodes=1, version={}): self.deleteKubernetesClusterAndVerify(cluster.id, False, True) self.debug("No valid cluster, need to deploy a new one") - return self.createNewKubernetesCluster(version, size, control_nodes) + return self.createNewKubernetesCluster(version, size, control_nodes, etcd_nodes=etcd_nodes, + worker_offering=worker_offering, control_offering=control_offering, + etcd_offering=etcd_offering) - def createNewKubernetesCluster(self, version, size, control_nodes) : + def createNewKubernetesCluster(self, version, size, control_nodes, etcd_nodes=0, + worker_offering=None, control_offering=None, etcd_offering=None): name = 'testcluster-' + random_gen() self.debug("Creating for Kubernetes cluster with name %s" % name) try: - cluster = self.createKubernetesCluster(name, version.id, size, control_nodes) + cluster = self.createKubernetesCluster(name, version.id, size, control_nodes, etcd_nodes=etcd_nodes, + workers_offering=worker_offering, control_offering=control_offering, + etcd_offering=etcd_offering) self.verifyKubernetesCluster(cluster, name, version.id, size, control_nodes) except Exception as ex: cluster = self.listKubernetesCluster(cluster_name = name) diff --git a/test/integration/smoke/test_safe_shutdown.py b/test/integration/smoke/test_ms_maintenance_and_safe_shutdown.py similarity index 58% rename from test/integration/smoke/test_safe_shutdown.py rename to test/integration/smoke/test_ms_maintenance_and_safe_shutdown.py index d757bb6d0689..4eff33122fed 100644 --- a/test/integration/smoke/test_safe_shutdown.py +++ b/test/integration/smoke/test_ms_maintenance_and_safe_shutdown.py @@ -22,19 +22,19 @@ from marvin.lib.base import * from marvin.lib.common import * -class TestSafeShutdown(cloudstackTestCase): +class TestMSMaintenanceAndSafeShutdown(cloudstackTestCase): """ - Tests safely shutting down the Management Server + Tests MS maintenance and safe shutting down the Management Server """ def setUp(self): self.apiclient = self.testClient.getApiClient() + self.hypervisor = self.testClient.getHypervisorInfo() self.mgtSvrDetails = self.config.__dict__["mgtSvr"][0].__dict__ self.cleanup = [] def tearDown(self): - self.startServer() - super(TestSafeShutdown, self).tearDown() + super(TestMSMaintenanceAndSafeShutdown, self).tearDown() def isServerShutdown(self): sshClient = SshClient( @@ -87,8 +87,61 @@ def run_async_cmd(self) : {"name": "test", "displaytext": "test"} ) + def getActiveManagementServers(self): + cmd = listManagementServers.listManagementServersCmd() + servers = self.apiclient.listManagementServers(cmd) + active_servers = [] + for idx, server in enumerate(servers): + if server.state == 'Up': + active_servers.append(server.serviceip) + return active_servers + + @attr(tags=["advanced", "smoke"]) + def test_01_prepare_and_cancel_maintenance(self): + active_management_servers = self.getActiveManagementServers() + if len(active_management_servers) <= 1: + self.skipTest("Skipping test case, this test is intended for only multiple management servers") + + hypervisor = self.hypervisor.lower() + if hypervisor == 'kvm': + list_configurations_cmd = listConfigurations.listConfigurationsCmd() + list_configurations_cmd.name = "host" + list_configurations_response = self.apiclient.listConfigurations(list_configurations_cmd) + self.assertNotEqual(len(list_configurations_response), 0, + "Check if the list configurations API returns a non-empty response") + + for item in list_configurations_response: + if item.name == list_configurations_cmd.name: + host_config = item + + hosts = host_config.value.split(",") + if len(hosts) <= 1: + self.skipTest( + "Skipping test case, this test is intended for only multiple management server hosts configured on host setting for kvm") + + try : + prepare_for_maintenance_cmd = prepareForMaintenance.prepareForMaintenanceCmd() + prepare_for_maintenance_cmd.managementserverid = 1 + response = self.apiclient.prepareForMaintenance(prepare_for_maintenance_cmd) + self.assertEqual( + response.maintenanceinitiated, + True, + "Failed to prepare for maintenance" + ) + try : + self.run_async_cmd() + except Exception as e: + self.debug("Prepare for maintenance check successful, API failure: %s" % e) + finally : + cancel_maintenance_cmd = cancelMaintenance.cancelMaintenanceCmd() + cancel_maintenance_cmd.managementserverid = 1 + self.apiclient.cancelMaintenance(cancel_maintenance_cmd) + ## Just to be sure, run another async command + project = self.run_async_cmd() + self.cleanup.append(project) + @attr(tags=["advanced", "smoke"]) - def test_01_prepare_and_cancel_shutdown(self): + def test_02_prepare_and_cancel_shutdown(self): try : prepare_for_shutdown_cmd = prepareForShutdown.prepareForShutdownCmd() prepare_for_shutdown_cmd.managementserverid = 1 @@ -111,7 +164,7 @@ def test_01_prepare_and_cancel_shutdown(self): self.cleanup.append(project) @attr(tags=["advanced", "smoke"]) - def test_02_trigger_shutdown(self): + def test_03_trigger_shutdown(self): try : cmd = triggerShutdown.triggerShutdownCmd() cmd.managementserverid = 1 diff --git a/test/integration/smoke/test_network.py b/test/integration/smoke/test_network.py index b3e7fd3e42f4..3a0db0feafee 100644 --- a/test/integration/smoke/test_network.py +++ b/test/integration/smoke/test_network.py @@ -470,10 +470,8 @@ def test_01_port_fwd_on_src_nat(self): except Exception as e: self.fail("NAT Rule Deletion Failed: %s" % e) - # NAT rule listing should fail as the nat rule does not exist - with self.assertRaises(Exception): - list_nat_rules(self.apiclient, - id=nat_rule.id) + nat_rules_after_deletion = list_nat_rules(self.apiclient, id=nat_rule.id) + self.assertEqual(nat_rules_after_deletion, None, "Check that the port forwarding rule has been deleted.") # Check if the Public SSH port is inaccessible with self.assertRaises(Exception): @@ -585,13 +583,8 @@ def test_02_port_fwd_on_non_src_nat(self): nat_rule.delete(self.apiclient) - try: - list_nat_rule_response = list_nat_rules( - self.apiclient, - id=nat_rule.id - ) - except CloudstackAPIException: - logger.debug("Nat Rule is deleted") + nat_rules_after_deletion = list_nat_rules(self.apiclient, id=nat_rule.id) + self.assertEqual(nat_rules_after_deletion, None, "Check that the port forwarding rule has been deleted.") # Check if the Public SSH port is inaccessible with self.assertRaises(Exception): diff --git a/test/integration/smoke/test_network_acl.py b/test/integration/smoke/test_network_acl.py index 0914f4dc2d4e..b6a660a1156b 100644 --- a/test/integration/smoke/test_network_acl.py +++ b/test/integration/smoke/test_network_acl.py @@ -125,6 +125,32 @@ def test_network_acl(self): self.assertTrue(vm.state == 'Running', "VM is not running") self.debug("VM %s deployed in VPC %s" %(vm.id, vpc.id)) + # 6) Acquire a Public IP, and add Load Balancing Rule + public_ip = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.domain.id, + vpcid=vpc.id + ) + LoadBalancerRule.create( + self.apiclient, + self.services["lbrule"], + ipaddressid=public_ip.ipaddress.id, + accountid=self.account.name, + vpcid=vpc.id, + networkid=ntwk.id, + domainid=self.account.domainid) + + # 7) Add Port Forwarding Rule with same Public IP to test conserve mode + NATRule.create( + self.apiclient, + vm, + self.services["natrule"], + ipaddressid=public_ip.ipaddress.id, + vpcid=vpc.id, + networkid=ntwk.id) + @classmethod def tearDownClass(cls): try: diff --git a/test/integration/smoke/test_network_extension_namespace.py b/test/integration/smoke/test_network_extension_namespace.py new file mode 100644 index 000000000000..5452cd4ac9d0 --- /dev/null +++ b/test/integration/smoke/test_network_extension_namespace.py @@ -0,0 +1,3308 @@ +# 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. +"""Support module for the NetworkExtension smoke tests. + +**Do not run this file directly.** Tests are discovered and executed through +``test_network_extension_namespace.py``, which exposes the test class under a +discoverable name. + +This module provides: + * Module-level constants (script names, URLs, capabilities JSON). + * Helper functions (_download_script, _ensure_scripts_downloaded, etc.). + * Deployer classes (MgmtServerDeployer, KvmHostDeployer). + * Base test class ``_TestNetworkExtensionNamespace`` (underscore = not + collected directly by nose/Marvin). + +Renamed from ``test_network_extension_provider.py``. The canonical test file +is ``test_network_extension_namespace.py``. +""" +import json +import logging +import os +import random +import shutil +import stat +import subprocess +import tempfile +import time +import urllib.parse + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import (listPhysicalNetworks, + listTrafficTypes, + listManagementServers, + listNetworkServiceProviders, + updateNetworkServiceProvider, + deleteNetworkServiceProvider, + createFirewallRule, + deleteFirewallRule, + listPublicIpAddresses, + createGuestNetworkIpv6Prefix, + listGuestNetworkIpv6Prefixes, + deleteGuestNetworkIpv6Prefix) +from marvin.lib.base import (Account, + Configurations, + Extension, + ExtensionCustomAction, + Host, + LoadBalancerRule, + Network, + NetworkACL, + NetworkACLList, + NetworkOffering, + NATRule, + NIC, + PublicIPAddress, + PublicIpRange, + ServiceOffering, + SSHKeyPair, + StaticNATRule, + Template, + VirtualMachine, + VPC, + VpcOffering) +from marvin.lib.common import (get_domain, get_zone, get_template) +from marvin.lib.utils import cleanup_resources, random_gen +from marvin.sshClient import SshClient +from nose.plugins.attrib import attr + +_multiprocess_shared_ = True + +# The file names of the scripts to deploy on the management server and KVM hosts. +SCRIPT_FILENAME = 'network-namespace-wrapper.sh' +ENTRY_POINT_FILENAME = 'network-namespace.sh' + +# Remote URLs to download the scripts from +_GITHUB_BASE = ( + 'https://raw.githubusercontent.com/apache/cloudstack-extensions' + '/refs/heads/network-namespace/Network-Namespace/' +) +WRAPPER_SCRIPT_URL = _GITHUB_BASE + SCRIPT_FILENAME +ENTRY_POINT_SCRIPT_URL = _GITHUB_BASE + ENTRY_POINT_FILENAME + +# Local cache paths (downloaded once, reused across test methods) +_THIS_DIR = os.path.dirname(os.path.abspath(__file__)) +_SCRIPT_CACHE_DIR = os.path.join(tempfile.gettempdir(), 'cs-extnet-script-cache') +WRAPPER_SCRIPT_LOCAL = os.path.join(_SCRIPT_CACHE_DIR, SCRIPT_FILENAME) +ENTRY_POINT_SCRIPT_LOCAL = os.path.join(_SCRIPT_CACHE_DIR, ENTRY_POINT_FILENAME) + +# Network services — comma-separated list of all services this extension supports. +# Tests select a subset when creating NetworkOfferings. +NETWORK_SERVICES = ( + "Dhcp,Dns,UserData," + "SourceNat,StaticNat,PortForwarding,Firewall,Lb,NetworkACL,CustomAction" +) + +# Per-service capabilities JSON object (no "services" wrapper). +NETWORK_SERVICE_CAPABILITIES_JSON = json.dumps({ + "Lb": { + "SupportedLBAlgorithms": "roundrobin,leastconn,source", + "SupportedLBIsolation": "dedicated", + "SupportedProtocols": "tcp,udp,tcp-proxy", + "SupportedStickinessMethods": "lbcookie,appsession", + "LbSchemes": "Public", + "SslTermination": "false", + "VmAutoScaling": "false" + }, + "Firewall": { + "TrafficStatistics": "per public ip", + "SupportedProtocols": "tcp,udp,icmp", + "SupportedEgressProtocols": "tcp,udp,icmp,all", + "SupportedTrafficDirection": "ingress,egress", + "MultipleIps": "true" + }, + "Dns": { + "AllowDnsSuffixModification": "true", + "ExternalDns": "true" + }, + "Dhcp": { + "DhcpAccrossMultipleSubnets": "true" + }, + "Gateway": { + "RedundantRouter": "false" + }, + "SourceNat": { + "SupportedSourceNatTypes": "peraccount", + "RedundantRouter": "false" + }, + "StaticNat": { + "Supported": "true" + }, + "PortForwarding": { + "SupportedProtocols": "tcp,udp" + }, + "UserData": { + "Supported": "true" + }, + "NetworkACL": { + "SupportedProtocols": "tcp,udp,icmp" + }, + "CustomAction": { + "Supported": "true" + } +}) + +ISOLATED_NETWORK_SERVICES = ( + "Dhcp,Dns,UserData," + "SourceNat,StaticNat,PortForwarding,Firewall,Lb,CustomAction" +) + +VPC_NETWORK_SERVICES = ( + "Dhcp,Dns,UserData," + "SourceNat,StaticNat,PortForwarding,Lb,NetworkACL,CustomAction" +) + +# --------------------------------------------------------------------------- +# Script download helpers +# --------------------------------------------------------------------------- + +def _download_script(url, dest_path): + """Download *url* to *dest_path* via curl or wget and make it executable.""" + os.makedirs(os.path.dirname(dest_path), exist_ok=True) + log = logging.getLogger('cs-extnet') + log.info("Downloading %s -> %s", url, dest_path) + for cmd in (['curl', '-fsSL', url, '-o', dest_path], + ['wget', '-q', url, '-O', dest_path]): + if subprocess.run(cmd, check=False).returncode == 0: + os.chmod(dest_path, stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + return dest_path + raise RuntimeError("Failed to download %s with curl and wget" % url) + + +def _ensure_scripts_downloaded(): + """Download both scripts from GitHub if not already cached. + + If GitHub is unreachable the function falls back to the corresponding + scripts in the local source tree so that tests can run offline: + * wrapper → extensions/network-namespace/network-namespace-wrapper.sh + * entry point → extensions/network-namespace/network-namespace.sh + + Returns (wrapper_path, entry_point_path). + """ + _src_root = os.path.normpath( + os.path.join(os.path.dirname(os.path.abspath(__file__)), + '..', '..', '..')) + + try: + _download_script(WRAPPER_SCRIPT_URL, WRAPPER_SCRIPT_LOCAL) + except Exception: + # Offline fallback: deploy the source-tree implementation. + _local_impl = os.path.join( + _src_root, 'extensions', 'network-namespace', + 'network-namespace-wrapper.sh') + if os.path.exists(_local_impl): + os.makedirs(os.path.dirname(WRAPPER_SCRIPT_LOCAL), + exist_ok=True) + shutil.copy2(_local_impl, WRAPPER_SCRIPT_LOCAL) + os.chmod(WRAPPER_SCRIPT_LOCAL, + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + logging.getLogger('cs-extnet').info( + "Offline fallback: using local %s as %s", + _local_impl, WRAPPER_SCRIPT_LOCAL) + else: + raise + + try: + _download_script(ENTRY_POINT_SCRIPT_URL, ENTRY_POINT_SCRIPT_LOCAL) + except Exception: + _local_ep = os.path.join( + _src_root, 'extensions', 'network-namespace', + 'network-namespace.sh') + if os.path.exists(_local_ep): + os.makedirs(os.path.dirname(ENTRY_POINT_SCRIPT_LOCAL), + exist_ok=True) + shutil.copy2(_local_ep, ENTRY_POINT_SCRIPT_LOCAL) + os.chmod(ENTRY_POINT_SCRIPT_LOCAL, + stat.S_IRWXU | stat.S_IRGRP | stat.S_IXGRP | + stat.S_IROTH | stat.S_IXOTH) + logging.getLogger('cs-extnet').info( + "Offline fallback: using local %s as %s", + _local_ep, ENTRY_POINT_SCRIPT_LOCAL) + else: + raise + + return WRAPPER_SCRIPT_LOCAL, ENTRY_POINT_SCRIPT_LOCAL + + +# --------------------------------------------------------------------------- +# KVM host discovery helpers (from Marvin config) +# --------------------------------------------------------------------------- + +def _get_kvm_hosts_from_config(config): + """Return list of host dicts for all KVM hosts in the Marvin config. + + Each entry: {"ip": .., "username": .., "password": ..} + """ + hosts = [] + try: + for zone in config.__dict__.get("zones", []): + for pod in zone.__dict__.get("pods", []): + for cluster in pod.__dict__.get("clusters", []): + for h in cluster.__dict__.get("hosts", []): + if hasattr(h, '__dict__'): + h = h.__dict__ + url = h.get("url", "") + if not url.startswith('http://') and not url.startswith('https://'): + url = 'http://' + url + parsed = urllib.parse.urlparse(url) + ip = parsed.hostname or '' + if not ip: + continue + hosts.append({ + "ip": ip, + "username": h.get("username", "root"), + "password": h.get("password", ""), + }) + except Exception as e: + logging.getLogger('cs-extnet').warning( + "Could not read KVM hosts from config: %s", e) + return hosts + + +# --------------------------------------------------------------------------- +# SSH helper +# --------------------------------------------------------------------------- + +def _ssh_copy_file(host_ip, host_port, username, password, local_path, remote_path): + """Transfer *local_path* to *remote_path* on *host_ip* via SshClient.""" + ssh = SshClient(host_ip, int(host_port), username, password) + ssh.execute("mkdir -p '%s'" % os.path.dirname(remote_path)) + # Use SFTP upload to avoid very large shell arguments for script content. + ssh.scp(local_path, remote_path) + ssh.execute("chmod 755 '%s'" % remote_path) + + +# --------------------------------------------------------------------------- +# MgmtServerDeployer – deploys network-namespace.sh to the management server +# --------------------------------------------------------------------------- + +class MgmtServerDeployer: + """Copies network-namespace.sh to the management server via SSH.""" + + def __init__(self, mgt_details, logger=None): + self.ip = mgt_details.get("mgtSvrIp", "localhost") + self.port = 22 + self.user = mgt_details.get("user", "root") + self.passwd = mgt_details.get("passwd", "") + self.logger = logger or logging.getLogger('MgmtServerDeployer') + + def copy_file(self, local_path, remote_path, mode='0755'): + _ssh_copy_file(self.ip, self.port, self.user, self.passwd, + local_path, remote_path) + self.logger.info("Copied %s -> %s on mgmt %s", + local_path, remote_path, self.ip) + + def remove_file(self, remote_path): + try: + SshClient(self.ip, self.port, self.user, self.passwd).execute( + "rm -f '%s'" % remote_path) + except Exception as e: + self.logger.warning("Could not remove %s: %s", remote_path, e) + + +# --------------------------------------------------------------------------- +# KvmHostDeployer – deploys network-namespace-wrapper.sh to KVM hosts +# --------------------------------------------------------------------------- + +class KvmHostDeployer: + """Copies the KVM wrapper script to all KVM hosts via SSH. + + *dest_path* is the absolute path on each KVM host where the wrapper + script is installed. This path differs from the management-server + entry-point path: + management server: /usr/share/cloudstack-management/extensions//.sh + KVM host (wrapper): /etc/cloudstack/extensions//-wrapper.sh + """ + + def __init__(self, config_hosts=None, logger=None, dest_path=None): + self.config_hosts = config_hosts or [] + self.logger = logger or logging.getLogger('KvmHostDeployer') + self._deployed_hosts = [] + self._dest_path = dest_path + + def deploy(self): + """Deploy wrapper to all configured hosts. Returns list of deployed IPs.""" + wrapper_path, _ = _ensure_scripts_downloaded() + self._deployed_hosts = [] + if not self.config_hosts: + self.logger.warning("No KVM hosts configured — wrapper not deployed") + return [] + for h in self.config_hosts: + ip = h.get('ip', '') + username = h.get('username', 'root') + password = h.get('password', '') + if not ip: + continue + self.logger.info("Deploying wrapper to KVM host %s at %s", + ip, self._dest_path) + try: + _ssh_copy_file(ip, 22, username, password, + wrapper_path, self._dest_path) + self.logger.info("Deployed wrapper to %s at %s", + ip, self._dest_path) + self._deployed_hosts.append(ip) + except Exception as e: + self.logger.warning("Failed deploying to %s: %s", ip, e) + return self._deployed_hosts + + def host_ips_csv(self): + """Return comma-separated IP list of all configured hosts.""" + return ','.join(h.get('ip', '') for h in self.config_hosts if h.get('ip')) + + def remove_file(self, remote_path): + """Remove wrapper script from all deployed KVM hosts.""" + if not self._deployed_hosts: + self.logger.debug("No KVM hosts deployed — skipping remove_file") + return + for h in self.config_hosts: + ip = h.get('ip', '') + username = h.get('username', 'root') + password = h.get('password', '') + if not ip or ip not in self._deployed_hosts: + continue + try: + SshClient(ip, 22, username, password).execute( + "rm -f '%s'" % remote_path) + self.logger.info("Removed %s from KVM host %s", remote_path, ip) + except Exception as e: + self.logger.warning("Could not remove %s from %s: %s", + remote_path, ip, e) + + +# --------------------------------------------------------------------------- +# Test class +# --------------------------------------------------------------------------- + +class TestNetworkExtensionNamespace(cloudstackTestCase): + """Smoke tests for the NetworkExtension plugin. + + Not discovered directly — exposed as ``TestNetworkExtensionNamespace`` + through ``test_network_extension_namespace.py``. + + Covers: + test_01 — NSP state transitions (Disabled/Enabled/Disabled) + test_02 — network.services / network.service.capabilities details stored correctly + test_03 — extension enable/disable and delete restriction + test_04 — DHCP/DNS/UserData: cloud-init VM on a shared network reaches Running state + test_05 — full isolated lifecycle: static NAT, PF, LB, restart + (all with SSH connectivity verification via keypair) + test_06 — VPC multi-tier + VPC restart with SSH verification + test_07 — VPC Network ACL testing with multiple tiers and traffic rules + test_08 — custom-action smoke for Policy-Based Routing (PBR) actions on isolated network + test_09 — VPC source NAT IP update without VPC restart + test_10 — custom-action smoke for Policy-Based Routing (PBR) actions on VPC tier network + test_11 — IPv6 dualstack isolated network: namespace IPv6 address + radvd + """ + + @staticmethod + def _custom_action_details(resp): + """Extract best-effort details text from runCustomAction response.""" + if resp is None: + return "" + result = getattr(resp, 'result', None) + if isinstance(result, dict): + return result.get('details', '') or '' + if hasattr(result, '__dict__'): + return getattr(result, 'details', '') or '' + return '' + + def _run_custom_action_logged(self, action, resourceid, parameters=None): + """Run a custom action and log the request/response for smoke debugging.""" + action_name = getattr(action, 'name', None) or getattr(action, 'customactionname', None) or str(action) + action_id = getattr(action, 'id', None) + self.logger.info( + "runCustomAction command: action=%s id=%s resourceid=%s parameters=%s", + action_name, + action_id, + resourceid, + parameters if parameters is not None else [] + ) + if parameters is None: + out = action.run(self.apiclient, resourceid=resourceid) + else: + out = action.run(self.apiclient, resourceid=resourceid, parameters=parameters) + result = getattr(out, 'result', None) + message = '' + if isinstance(result, dict): + message = result.get('message', '') or '' + elif hasattr(result, '__dict__'): + message = getattr(result, 'message', '') or '' + self.logger.info( + "runCustomAction output: action=%s success=%s message=%s details=%s raw=%s", + action_name, + getattr(out, 'success', None), + message, + self._custom_action_details(out), + out + ) + return out + + @classmethod + def setUpClass(cls): + testClient = super(TestNetworkExtensionNamespace, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + # All management servers — entry-point script is deployed to every one. + cls.all_mgt_svr_details = [ + mgt.__dict__ if hasattr(mgt, '__dict__') else mgt + for mgt in cls.config.__dict__.get("mgtSvr", []) + ] or [cls.mgtSvrDetails] + cls.hv = testClient.getHypervisorInfo() + cls._cleanup = [] + cls.tmp_files = [] + cls.keypair = None + + cls.logger = logging.getLogger("TestNetworkExtensionNamespace") + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + cls.logger.info("Management servers (from config): %s", + [m.get("mgtSvrIp", "?") for m in cls.all_mgt_svr_details]) + + # Supplement / override management server list via listManagementServers API. + # All mgmt servers are assumed to share the same SSH credentials as the + # first entry in the Marvin config (mgtSvr[0]). + try: + ms_cmd = listManagementServers.listManagementServersCmd() + api_mgmt_servers = cls.apiclient.listManagementServers(ms_cmd) + if api_mgmt_servers: + base_creds = dict(cls.mgtSvrDetails) + api_mgt_details = [] + for ms in api_mgmt_servers: + ip = (getattr(ms, 'ipaddress', None) + or getattr(ms, 'ip', None) + or getattr(ms, 'hostname', None)) + if ip: + entry = dict(base_creds) + entry['mgtSvrIp'] = ip + api_mgt_details.append(entry) + if api_mgt_details: + cls.all_mgt_svr_details = api_mgt_details + cls.logger.info( + "Management servers (from listManagementServers API): %s", + [d.get('mgtSvrIp') for d in api_mgt_details]) + except Exception as _ms_err: + cls.logger.warning( + "Could not retrieve management servers via listManagementServers " + "API (%s); using config-provided list", _ms_err) + + # KVM host credentials from Marvin config + cls.kvm_host_configs = _get_kvm_hosts_from_config(cls.config) + cls.logger.info("KVM hosts from config: %s", + [h['ip'] for h in cls.kvm_host_configs if h.get('ip')]) + + # ---- Cloud-init template (Ubuntu 22.04) ---- + # Used for hardware tests that verify actual SSH connectivity. + # Falls back to the default test template when the cloud-init entry + # is absent from the services config (e.g. on simulator). + try: + tpl_data = (cls.services + .get("test_templates_cloud_init", {}) + .get(cls.hv.lower())) + if tpl_data: + cls.logger.info("Registering cloud-init template for %s", cls.hv) + tpl = Template.register( + cls.apiclient, + tpl_data, + zoneid=cls.zone.id, + hypervisor=cls.hv, + ) + tpl.download(cls.apiclient) + cls._cleanup.append(tpl) + cls.template = tpl + cls.logger.info("Cloud-init template registered: %s", tpl.id) + else: + cls.logger.info("No cloud-init template for %s; using default", + cls.hv) + cls.template = get_template(cls.apiclient, cls.zone.id, cls.hv) + except Exception as e: + cls.logger.warning("Cloud-init template registration failed: %s; " + "falling back to default", e) + cls.template = get_template(cls.apiclient, cls.zone.id, cls.hv) + + # ---- Download wrapper scripts from GitHub ---- + try: + _ensure_scripts_downloaded() + cls.logger.info("Scripts cached: %s %s", + WRAPPER_SCRIPT_LOCAL, ENTRY_POINT_SCRIPT_LOCAL) + except Exception as e: + cls.logger.warning("Could not download scripts from GitHub: %s", e) + + @classmethod + def tearDownClass(cls): + super(TestNetworkExtensionNamespace, cls).tearDownClass() + for tmp_file in cls.tmp_files: + try: + os.remove(tmp_file) + except Exception: + pass + + def setUp(self): + self.cleanup = [] + self.provider_id = None + self.physical_network = None + self.extension = None + self.extension_path = None + self.mgmt_deployer = None + self._mgmt_script_path = None + self._all_mgmt_deployers = [] + self.kvm_deployer = None + self._ssh_private_key_file = None + + def tearDown(self): + #self._safe_teardown() + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + self.logger.warning("cleanup_resources error: %s", e) + + # ------------------------------------------------------------------ + # CloudStack API helpers + # ------------------------------------------------------------------ + + def _get_physical_network(self): + """Return the physical network with Guest traffic type in the test zone.""" + cmd = listPhysicalNetworks.listPhysicalNetworksCmd() + cmd.zoneid = self.zone.id + pns = self.apiclient.listPhysicalNetworks(cmd) + self.assertIsInstance(pns, list) + self.assertGreater(len(pns), 0) + + for pn in pns: + tt_cmd = listTrafficTypes.listTrafficTypesCmd() + tt_cmd.physicalnetworkid = pn.id + traffic_types = self.apiclient.listTrafficTypes(tt_cmd) + if traffic_types: + for tt in traffic_types: + if getattr(tt, 'traffictype', '').lower() == 'guest': + self.logger.info( + "Selected physical network with Guest traffic: " + "%s (%s)", pn.name, pn.id) + return pn + + self.logger.info("No physical network with Guest traffic found; " + "using first: %s (%s)", pns[0].name, pns[0].id) + return pns[0] + + def _find_provider(self, phys_net_id, name): + cmd = listNetworkServiceProviders.listNetworkServiceProvidersCmd() + cmd.physicalnetworkid = phys_net_id + cmd.name = name + providers = self.apiclient.listNetworkServiceProviders(cmd) + return providers[0] if isinstance(providers, list) and providers else None + + def _update_provider_state(self, provider_id, state): + cmd = updateNetworkServiceProvider.updateNetworkServiceProviderCmd() + cmd.id = provider_id + cmd.state = state + return self.apiclient.updateNetworkServiceProvider(cmd) + + def _delete_provider(self, provider_id): + cmd = deleteNetworkServiceProvider.deleteNetworkServiceProviderCmd() + cmd.id = provider_id + self.apiclient.deleteNetworkServiceProvider(cmd) + + # ------------------------------------------------------------------ + # Script deployment helpers + # ------------------------------------------------------------------ + + def _deploy_scripts(self): + """Deploy scripts to all management servers and all KVM hosts. + + The entry-point (network-namespace.sh) is deployed to every management + server at ``self.extension_path`` (the path CloudStack assigned to the + extension, e.g. + ``/usr/share/cloudstack-management/extensions//.sh``). + + The KVM wrapper (network-namespace-wrapper.sh) is deployed to each KVM + host at a *different* path derived from the extension name: + ``/etc/cloudstack/extensions//-wrapper.sh`` + + The entry-point script uses the same derivation at runtime (see + DEFAULT_SCRIPT_PATH in network-namespace.sh) so that it always calls + the correct wrapper on the remote KVM host. + """ + wrapper_src, entry_point_src = _ensure_scripts_downloaded() + + self._mgmt_script_path = (self.extension_path or "").strip().rstrip('/') + + # Deploy entry-point to ALL management servers. + # all_mgt_svr_details is collected once in setUpClass from + # cls.config.__dict__["mgtSvr"] so every server in a HA pair is covered. + self._all_mgmt_deployers = [] + for mgt_details in self.all_mgt_svr_details: + deployer = MgmtServerDeployer(mgt_details, logger=self.logger) + deployer.copy_file(entry_point_src, self._mgmt_script_path) + self.logger.info("Entry-point deployed to mgmt %s at %s", + mgt_details.get("mgtSvrIp", "?"), + self._mgmt_script_path) + self._all_mgmt_deployers.append(deployer) + + # Keep mgmt_deployer pointing at the primary server for backward-compat + self.mgmt_deployer = (self._all_mgmt_deployers[0] + if self._all_mgmt_deployers + else MgmtServerDeployer(self.mgtSvrDetails, + logger=self.logger)) + + # Derive the wrapper destination path on KVM hosts from the extension path: + # mgmt: .../extensions//.sh + # kvm: /etc/cloudstack/extensions//-wrapper.sh + ext_dir_name = os.path.basename(os.path.dirname(self._mgmt_script_path)) + script_basename = os.path.splitext( + os.path.basename(self._mgmt_script_path))[0] + kvm_wrapper_path = "/etc/cloudstack/extensions/%s/%s-wrapper.sh" % ( + ext_dir_name, script_basename) + + self.kvm_deployer = KvmHostDeployer( + config_hosts=self.kvm_host_configs, + logger=self.logger, + dest_path=kvm_wrapper_path, + ) + deployed = self.kvm_deployer.deploy() + self.logger.info( + "KVM wrapper deployed to %d host(s) at %s: %s", + len(deployed), kvm_wrapper_path, deployed) + + def _cleanup_mgmt_script(self): + if self._mgmt_script_path: + all_deployers = getattr(self, '_all_mgmt_deployers', None) or [] + if all_deployers: + for deployer in all_deployers: + deployer.remove_file(self._mgmt_script_path) + elif self.mgmt_deployer: + self.mgmt_deployer.remove_file(self._mgmt_script_path) + self._mgmt_script_path = None + + def _cleanup_kvm_script(self): + """Remove KVM wrapper script from all deployed KVM hosts.""" + if self.kvm_deployer and self.kvm_deployer._dest_path: + self.kvm_deployer.remove_file(self.kvm_deployer._dest_path) + + # ------------------------------------------------------------------ + # Teardown helper + # ------------------------------------------------------------------ + + def _safe_teardown(self): + """Best-effort cleanup of extension/NSP/scripts.""" + if self.extension and self.physical_network: + try: + self.extension.unregister(self.apiclient, + self.physical_network.id, + 'PhysicalNetwork') + except Exception: + pass + if self.provider_id: + for fn in (lambda: self._update_provider_state(self.provider_id, 'Disabled'), + lambda: self._delete_provider(self.provider_id)): + try: + fn() + except Exception: + pass + self.provider_id = None + if self.extension: + try: + self.extension.delete(self.apiclient, + unregisterresources=False, + removeactions=False) + except Exception: + pass + self.extension = None + self._cleanup_mgmt_script() + self._cleanup_kvm_script() + + # ------------------------------------------------------------------ + # SSH helpers (provider-agnostic — no KVM namespace checks) + # ------------------------------------------------------------------ + + def _verify_vm_ssh_access(self, ip, port=22, timeout=30, retries=10): + """Return True if SSH to *ip*:*port* succeeds using the active keypair. + + The VM is expected to run Ubuntu 22.04 (cloud-init) with username + ``ubuntu``. Returns False if no keypair is available or if the + connection fails. + """ + key_file = self._ssh_private_key_file + if not key_file and self.keypair: + key_file = getattr(self.keypair, 'private_key_file', None) + + if not key_file: + self.logger.warning("No SSH keypair available; returning False") + return False + try: + ssh = SshClient( + ip, int(port), "ubuntu", None, + keyPairFiles=key_file, + timeout=timeout, + retries=retries, + ) + out = ssh.execute("echo EXTNET_SSH_OK") + return any("EXTNET_SSH_OK" in line for line in out) + except Exception as e: + self.logger.warning("SSH to %s:%s failed: %s", ip, port, e) + return False + + def _assert_vm_ssh_accessible(self, ip, port=22, msg=None): + """Assert that SSH to *ip*:*port* succeeds.""" + result = self._verify_vm_ssh_access(ip, port) + self.assertTrue( + result, + msg or "SSH to %s:%s should be accessible" % (ip, port)) + + def _assert_vm_ssh_not_accessible(self, ip, port=22, msg=None): + """Assert that SSH to *ip*:*port* fails (uses short timeout).""" + result = self._verify_vm_ssh_access(ip, port, timeout=5, retries=1) + self.assertFalse( + result, + msg or "SSH to %s:%s should NOT be accessible" % (ip, port)) + + def _create_firewall_rule_for_ssh(self, ipaddressid): + """Create an ingress TCP/22 firewall rule on *ipaddressid*. + + Returns the created rule ID. + """ + cmd = createFirewallRule.createFirewallRuleCmd() + cmd.ipaddressid = ipaddressid + cmd.protocol = 'TCP' + cmd.startport = 22 + cmd.endport = 22 + cmd.cidrlist = ['0.0.0.0/0'] + rule = self.apiclient.createFirewallRule(cmd) + self.assertIsNotNone(rule, "createFirewallRule returned None") + self.logger.info("FW rule (TCP/22) created: id=%s on ipaddressid=%s", + rule.id, ipaddressid) + return rule.id + + def _delete_firewall_rule(self, fw_rule_id): + """Delete a firewall rule by ID (best-effort, warns on failure).""" + if not fw_rule_id: + return + cmd = deleteFirewallRule.deleteFirewallRuleCmd() + cmd.id = fw_rule_id + try: + self.apiclient.deleteFirewallRule(cmd) + self.logger.info("FW rule %s deleted", fw_rule_id) + except Exception as e: + self.logger.warning("Could not delete FW rule %s: %s", fw_rule_id, e) + + def _get_source_nat_ip(self, network_id): + """Return the source NAT public IP object for *network_id*, or None.""" + cmd = listPublicIpAddresses.listPublicIpAddressesCmd() + cmd.networkid = network_id + cmd.issourcenat = True + try: + result = self.apiclient.listPublicIpAddresses(cmd) + if isinstance(result, list) and result: + return result[0] + except Exception as e: + self.logger.warning("_get_source_nat_ip(%s): %s", network_id, e) + return None + + def _list_vpc_public_ips(self, vpc_id): + """Return all public IP objects associated with *vpc_id*.""" + cmd = listPublicIpAddresses.listPublicIpAddressesCmd() + cmd.vpcid = vpc_id + cmd.listall = True + try: + result = self.apiclient.listPublicIpAddresses(cmd) + if isinstance(result, list): + return result + except Exception as e: + self.logger.warning("_list_vpc_public_ips(%s): %s", vpc_id, e) + return [] + + def _wait_for_vpc_source_nat_ip(self, vpc_id, expected_ip=None, + retries=24, interval=5): + """Wait until one source-NAT IP exists for the VPC (and optionally matches expected_ip).""" + for _ in range(retries): + ips = self._list_vpc_public_ips(vpc_id) + src = [ip for ip in ips if getattr(ip, 'issourcenat', False)] + if len(src) == 1: + current_ip = getattr(src[0], 'ipaddress', None) + if expected_ip is None or current_ip == expected_ip: + return src[0] + time.sleep(interval) + return None + + # ------------------------------------------------------------------ + # KVM host prerequisite check (test_04 / test_05 / test_06) + # ------------------------------------------------------------------ + + def _check_kvm_host_prerequisites(self, tools=None): + """Verify that each configured KVM host has the required tools installed. + + Checks for the presence of every tool in *tools* (default: + ``['arping', 'dnsmasq', 'haproxy']``) on each host in + ``self.kvm_host_configs`` via SSH. The test is skipped (via + ``skipTest``) if any tool is absent from any reachable host. + + Hosts that cannot be reached over SSH are logged as warnings and + excluded from the check — the connectivity failure will surface + naturally when the test later tries to deploy scripts. + """ + if tools is None: + tools = ['arping', 'dnsmasq', 'haproxy'] + if not self.kvm_host_configs: + self.skipTest("No KVM hosts configured — skipping prerequisite check") + + missing_per_host = {} + for h in self.kvm_host_configs: + ip = h.get('ip', '') + if not ip: + continue + username = h.get('username', 'root') + password = h.get('password', '') + try: + ssh = SshClient(ip, 22, username, password) + missing_tools = [] + for tool in tools: + # Try both `command -v` (bash built-in) and `which` + out = ssh.execute( + "command -v {t} 2>/dev/null || which {t} 2>/dev/null" + " || echo MISSING_{t}".format(t=tool)) + found = any( + line.strip() and 'MISSING_' + tool not in line + for line in out + ) + if not found: + missing_tools.append(tool) + if missing_tools: + missing_per_host[ip] = missing_tools + self.logger.warning( + "KVM host %s is missing prerequisite(s): %s", + ip, ', '.join(missing_tools)) + else: + self.logger.info( + "KVM host %s: all prerequisites present (%s)", + ip, ', '.join(tools)) + except Exception as e: + self.logger.warning( + "Could not check prerequisites on KVM host %s: %s", ip, e) + + if missing_per_host: + detail = "; ".join( + "%s missing %s" % (ip, ', '.join(t)) + for ip, t in missing_per_host.items() + ) + self.skipTest( + "Skipping test — required tools not installed on KVM host(s): " + + detail) + + # ------------------------------------------------------------------ + # Extension + NSP + offering setup helper (shared by tests 04-06) + # ------------------------------------------------------------------ + + def _setup_extension_nsp_offering(self, ext_name_prefix, + supported_services=None, + guestiptype="Isolated", + for_vpc=False): + """Create extension, deploy scripts, register to physical network, + enable NSP, and optionally create a NetworkOffering. + + *supported_services* is a comma-separated list of CloudStack service names. + + *guestiptype* controls the guest IP type for the NetworkOffering: + ``"Isolated"`` (default) or ``"Shared"``. + + *for_vpc* — when ``True`` the NetworkOffering creation step is skipped. + VPC tests create their own VPC tier offering in the test body after this + helper returns, so creating a generic isolated offering here would be + wasteful and misleading. ``(None, ext_name)`` is returned in that case. + + Sets ``self.physical_network``, ``self.extension``, + ``self.extension_path``, ``self.provider_id``, + ``self.kvm_deployer``, ``self.mgmt_deployer``. + + Returns ``(nw_offering, ext_name)``. *nw_offering* is ``None`` when + *for_vpc* is ``True``. Skips when no KVM hosts are available. + """ + _svc = supported_services + self.physical_network = self._get_physical_network() + + ext_name = "%s-%s" % (ext_name_prefix, random_gen()) + self.extension = Extension.create( + self.apiclient, + name=ext_name, + type='NetworkOrchestrator', + details=[ + {"network.services": NETWORK_SERVICES}, + {"network.service.capabilities": NETWORK_SERVICE_CAPABILITIES_JSON}, + ] + ) + self.assertIsNotNone(self.extension) + self.assertEqual('Enabled', self.extension.state) + + ext_list = Extension.list(self.apiclient, id=self.extension.id) + self.assertTrue(ext_list and len(ext_list) > 0) + self.extension_path = ext_list[0].path + self.assertIsNotNone(self.extension_path) + self.logger.info("Extension '%s' created, path=%s", + ext_name, self.extension_path) + + # Deploy scripts + self._deploy_scripts() + kvm_hosts_csv = self.kvm_deployer.host_ips_csv() + if not kvm_hosts_csv: + self.skipTest("No KVM hosts available — skipping") + + # Register extension to physical network + register_details = [ + {"hosts": kvm_hosts_csv}, + {"username": self.kvm_host_configs[0].get('username', 'root')}, + {"password": self.kvm_host_configs[0].get('password', '')}, + ] + + self.extension.register( + self.apiclient, + self.physical_network.id, + 'PhysicalNetwork', + details=register_details + ) + self.logger.info("Extension registered, hosts=%s", kvm_hosts_csv) + + # Enable NSP + provider = self._find_provider(self.physical_network.id, ext_name) + self.assertIsNotNone(provider, + "NSP '%s' not found after registration" % ext_name) + self.provider_id = provider.id + if provider.state != 'Enabled': + self._update_provider_state(provider.id, 'Enabled') + self.assertEqual('Enabled', + self._find_provider(self.physical_network.id, + ext_name).state) + self.logger.info("NSP '%s' enabled", ext_name) + + # Create NetworkOffering — skipped for VPC tests because the caller + # creates a VPC tier offering separately after this helper returns. + if for_vpc: + self.logger.info( + "for_vpc=True: skipping isolated NetworkOffering creation " + "(VPC tier offering will be created in the test body)") + return None, ext_name + + _provider_map = {s.strip(): ext_name for s in _svc.split(',')} + offering_params = { + "name": "ExtNet-Offering-%s" % random_gen(), + "displaytext": "ExtNet test offering", + "guestiptype": guestiptype, + "traffictype": "GUEST", + "supportedservices": _svc, + "serviceProviderList": _provider_map, + } + if guestiptype == "Shared": + # CloudStack requires shared guest offerings to explicitly allow + # caller-specified IP ranges. + offering_params["specifyIpRanges"] = True + offering_params["specifyVlan"] = True + if guestiptype == "Isolated" and "SourceNat" in _svc: + offering_params["serviceCapabilityList"] = { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + } + nw_offering = NetworkOffering.create(self.apiclient, offering_params) + self.cleanup.append(nw_offering) + nw_offering.update(self.apiclient, state='Enabled') + self.logger.info("NetworkOffering '%s' enabled (services: %s)", + nw_offering.name, _svc) + + return nw_offering, ext_name + + def _create_account_keypair(self, account, name_suffix=""): + """Create an SSH keypair scoped to *account* and save private key file.""" + try: + kp_name = "extnet-%s-%s" % (name_suffix or random_gen(), random_gen()) + kp = SSHKeyPair.create( + self.apiclient, + name=kp_name, + account=account.name, + domainid=account.domainid, + ) + + pkfile = os.path.join(tempfile.gettempdir(), kp.name) + with open(pkfile, "w+") as fh: + fh.write(kp.privatekey) + os.chmod(pkfile, 0o400) + + self.tmp_files.append(pkfile) + kp.private_key_file = pkfile + self._ssh_private_key_file = pkfile + self.logger.info("Account keypair '%s' written to %s", kp.name, pkfile) + return kp + except Exception as e: + self.logger.warning("Could not create account keypair: %s", e) + return None + + def _create_account_network_vm(self, nw_offering, name_suffix="", + network_params=None): + """Create an account, an isolated network, and deploy a cloud-init VM. + + The VM is deployed with an account-scoped SSH keypair so that SSH + access can be tested directly. Username is ``ubuntu``. + + Returns ``(account, network, vm)``. + """ + suffix = name_suffix or random_gen() + + account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup.append(account) + + net_params = { + "name": "extnet-net-%s" % suffix, + "displaytext": "ExtNet test network %s" % suffix, + } + if network_params: + net_params.update(network_params) + + network = Network.create( + self.apiclient, + net_params, + accountid=account.name, + domainid=account.domainid, + networkofferingid=nw_offering.id, + zoneid=self.zone.id + ) + self.cleanup.insert(0, network) + self.assertIsNotNone(network) + self.logger.info("Network created: %s (%s)", network.name, network.id) + + svc_offering = ServiceOffering.list(self.apiclient, issystem=False)[0] + + vm_cfg = { + "displayname": "extnet-vm-%s" % suffix, + "name": "extnet-vm-%s" % suffix, + "zoneid": self.zone.id, + } + vm_kwargs = dict( + accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[network.id], + ) + account_keypair = self._create_account_keypair(account, suffix) + if account_keypair: + vm_kwargs["keypair"] = account_keypair.name + + vm = VirtualMachine.create(self.apiclient, vm_cfg, **vm_kwargs) + self.cleanup.insert(0, vm) + self.assertIsNotNone(vm) + self.logger.info("VM deployed: %s (%s)", vm.name, vm.id) + + return account, network, vm + + def _teardown_extension(self): + """Ordered teardown: disable NSP → delete provider → unregister + extension → delete extension → remove mgmt script.""" + self._update_provider_state(self.provider_id, 'Disabled') + self._delete_provider(self.provider_id) + self.provider_id = None + + self.extension.unregister(self.apiclient, + self.physical_network.id, 'PhysicalNetwork') + self.extension.delete(self.apiclient, + unregisterresources=False, removeactions=False) + self.extension = None + self.physical_network = None + self._cleanup_mgmt_script() + self._cleanup_kvm_script() + + # ------------------------------------------------------------------ + # Tests — API-only (no KVM / no SSH) + # ------------------------------------------------------------------ + + @attr(tags=["advanced", "smoke"], required_hardware="false") + def test_01_provider_state_transitions(self): + """NSP state machine: Disabled → Enabled → Disabled → Deleted.""" + pn = self._get_physical_network() + self.physical_network = pn + + ext_name = "extnet-nsp-" + random_gen() + self.extension = Extension.create( + self.apiclient, + name=ext_name, + type='NetworkOrchestrator', + details=[ + {"network.services": NETWORK_SERVICES}, + {"network.service.capabilities": NETWORK_SERVICE_CAPABILITIES_JSON}, + ] + ) + self.extension.register(self.apiclient, pn.id, 'PhysicalNetwork') + + provider = self._find_provider(pn.id, ext_name) + self.assertIsNotNone(provider) + self.provider_id = provider.id + + # Normalise to Disabled first + if provider.state == 'Enabled': + self._update_provider_state(provider.id, 'Disabled') + self.assertEqual('Disabled', self._find_provider(pn.id, ext_name).state) + + self._update_provider_state(provider.id, 'Enabled') + self.assertEqual('Enabled', self._find_provider(pn.id, ext_name).state) + self.logger.info("NSP enabled OK") + + self._update_provider_state(provider.id, 'Disabled') + self.assertEqual('Disabled', self._find_provider(pn.id, ext_name).state) + self.logger.info("NSP disabled OK") + + self._teardown_extension() + self.logger.info("test_01 PASSED") + + @attr(tags=["advanced", "smoke"], required_hardware="false") + def test_02_extension_capabilities_detail(self): + """Verify network.services and network.service.capabilities details are stored and retrievable via API.""" + svc_caps_json = json.dumps({ + "SourceNat": {"SupportedSourceNatTypes": "peraccount"} + }) + ext = Extension.create( + self.apiclient, + name="extnet-caps-" + random_gen(), + type='NetworkOrchestrator', + details=[ + {"network.services": "SourceNat"}, + {"network.service.capabilities": svc_caps_json}, + ] + ) + self.cleanup.append(ext) + ext_list = Extension.list(self.apiclient, id=ext.id) + self.assertTrue(ext_list and len(ext_list) > 0) + ext_obj = ext_list[0] + if hasattr(ext_obj, 'details') and ext_obj.details: + d = (ext_obj.details.__dict__ + if not isinstance(ext_obj.details, dict) + else ext_obj.details) + self.assertIn("network.services", d) + self.assertIn("SourceNat", d["network.services"].split(",")) + self.assertIn("network.service.capabilities", d) + stored_caps = json.loads(d["network.service.capabilities"]) + self.assertIn("SourceNat", stored_caps) + self.logger.info("test_02 PASSED") + + @attr(tags=["advanced", "smoke"], required_hardware="false") + def test_03_extension_enable_disable_and_delete_restriction(self): + """Extension enable/disable; deletion blocked while registered.""" + pn = self._get_physical_network() + self.physical_network = pn + + ext_name = "extnet-lifecycle-" + random_gen() + self.extension = Extension.create( + self.apiclient, + name=ext_name, + type='NetworkOrchestrator', + details=[ + {"network.services": NETWORK_SERVICES}, + {"network.service.capabilities": NETWORK_SERVICE_CAPABILITIES_JSON}, + ] + ) + self.assertIsNotNone(self.extension) + self.assertEqual('Enabled', self.extension.state, + "Extension should be Enabled by default") + + self.extension.update(self.apiclient, state='Disabled') + ext_list = Extension.list(self.apiclient, id=self.extension.id) + self.assertEqual('Disabled', ext_list[0].state) + self.logger.info("Extension disabled OK") + + self.extension.update(self.apiclient, state='Enabled') + ext_list = Extension.list(self.apiclient, id=self.extension.id) + self.assertEqual('Enabled', ext_list[0].state) + self.logger.info("Extension re-enabled OK") + + self.extension.register(self.apiclient, pn.id, 'PhysicalNetwork') + self.logger.info("Extension registered to physical network %s", pn.id) + + # Deletion while registered must fail + try: + self.extension.delete(self.apiclient, + unregisterresources=False, removeactions=False) + self.fail("Expected error when deleting extension while registered") + except Exception as e: + self.logger.info("Expected error when deleting while registered: %s", + e) + + self.extension.unregister(self.apiclient, pn.id, 'PhysicalNetwork') + self.extension.delete(self.apiclient, + unregisterresources=False, removeactions=False) + self.extension = None + self.physical_network = None + self.logger.info("test_03 PASSED") + + # ------------------------------------------------------------------ + # Tests — hardware required + # ------------------------------------------------------------------ + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_04_dhcp_dns_userdata(self): + """DHCP / DNS / UserData: cloud-init VM on a shared network reaches Running state. + + Creates a shared network with the extension providing Dhcp, Dns, + and UserData services. Deploys a VM with the cloud-init template. + Verifies the VM reaches Running state, which implies it received a + DHCP address from the extension. No SSH verification is performed. + + Steps: + 1. Set up extension + NSP + offering (Dhcp, Dns, UserData) with + guestiptype=Shared. + 2. Create account + shared network + cloud-init VM. + 3. Assert VM state == Running. + 4. Teardown. + """ + self._check_kvm_host_prerequisites(['arping', 'dnsmasq', 'haproxy']) + svc = "Dhcp,Dns,UserData" + nw_offering, _ext_name = self._setup_extension_nsp_offering( + "extnet-dhcp", supported_services=svc, guestiptype="Shared") + + # Shared offerings with specifyIpRanges=True require explicit range. + third_octet = random.randint(32, 220) + shared_params = { + "gateway": "172.31.%d.1" % third_octet, + "netmask": "255.255.255.0", + "startip": "172.31.%d.10" % third_octet, + "endip": "172.31.%d.200" % third_octet, + "vlan": "vlan://%d" % (3000 + third_octet), + } + + account, network, vm = self._create_account_network_vm( + nw_offering, name_suffix="dhcp", network_params=shared_params) + + # Verify VM is in Running state — DHCP must have worked + self.assertEqual( + 'Running', vm.state, + "VM should be in Running state after deploy (implies DHCP worked)") + self.logger.info("VM %s is Running — DHCP/DNS/UserData path exercised", + vm.name) + + # Cleanup + vm.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm] + network.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != network] + self._teardown_extension() + self.logger.info("test_04 PASSED") + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_05_isolated_network_full_lifecycle(self): + """Full isolated-network lifecycle with SSH connectivity verification. + + Uses a single cloud-init VM (Ubuntu 22.04, SSH keypair, username + ``ubuntu``) throughout. + + Sub-tests in order + ------------------ + A. Static NAT + allocate IP → enable static NAT → assert SSH blocked (no FW rule) + → create FW rule (TCP/22) → assert SSH works + → remove FW rule → assert SSH blocked (static NAT still active) + → re-add FW rule → assert SSH works + → disable static NAT → assert SSH fails + B. Port forwarding (22→22) + allocate IP → create PF rule → create FW rule (TCP/22) + → assert SSH works → delete PF rule → assert SSH fails + C. Load balancer (haproxy, round-robin TCP/22) + allocate IP → create LB rule → assign VM → create FW rule + → assert SSH works → remove VM from LB → delete LB rule + D. Network restart + allocate IP → create PF rule → create FW rule + → assert SSH works (baseline) + → restartNetwork(cleanup=True) + → assert SSH works (namespace rebuilt, rules reapplied) + """ + self._check_kvm_host_prerequisites(['arping', 'dnsmasq', 'haproxy']) + # ---- Setup ---- + svc = ISOLATED_NETWORK_SERVICES + nw_offering, _ext_name = self._setup_extension_nsp_offering( + "extnet-isolated", supported_services=svc) + account, network, vm = self._create_account_network_vm( + nw_offering, name_suffix="iso") + + # ============================================================== + # A. Static NAT + # + # StaticNATRule.enable() does NOT auto-create a firewall rule, so we + # must create one explicitly. StaticNATRule.disable() internally + # calls revokeFirewallRulesForIp(), which cascade-deletes our rule, + # so no explicit _delete_firewall_rule() is needed afterwards. + # ============================================================== + self.logger.info("--- Sub-test A: Static NAT ---") + snat_ip_obj = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=network.id + ) + snat_ip = snat_ip_obj.ipaddress.ipaddress + snat_ip_id = snat_ip_obj.ipaddress.id + + StaticNATRule.enable(self.apiclient, + ipaddressid=snat_ip_id, + virtualmachineid=vm.id, + networkid=network.id) + self.logger.info("Static NAT enabled on %s", snat_ip) + self._assert_vm_ssh_not_accessible( + snat_ip, 22, + "SSH via %s:22 should fail before firewall rule is created" % snat_ip) + + # Explicit FW rule required — static NAT does not auto-create one + snat_fw_rule_id = self._create_firewall_rule_for_ssh(snat_ip_id) + self._assert_vm_ssh_accessible( + snat_ip, 22, + "SSH via static NAT %s:22 should succeed" % snat_ip) + self.logger.info("Verified: SSH works via static NAT %s", snat_ip) + + # Remove the FW rule while static NAT is still active — VM must be blocked + self._delete_firewall_rule(snat_fw_rule_id) + self._assert_vm_ssh_not_accessible( + snat_ip, 22, + "SSH via %s:22 should fail after firewall rule removed (static NAT still active)" + % snat_ip) + self.logger.info("Verified: SSH blocked after FW rule removed (static NAT active)") + + # Re-add the FW rule — VM must be reachable again + self._create_firewall_rule_for_ssh(snat_ip_id) + self._assert_vm_ssh_accessible( + snat_ip, 22, + "SSH via %s:22 should succeed after firewall rule re-added" % snat_ip) + self.logger.info("Verified: SSH restored after FW rule re-added") + + # disable() cascades revokeFirewallRulesForIp() — deletes the FW rule + StaticNATRule.disable(self.apiclient, ipaddressid=snat_ip_id) + self.logger.info("Static NAT disabled on %s", snat_ip) + self._assert_vm_ssh_not_accessible( + snat_ip, 22, + "SSH via %s:22 should fail after static NAT disabled" % snat_ip) + self.logger.info("Verified: SSH fails after static NAT disabled") + snat_ip_obj.delete(self.apiclient) + + # ============================================================== + # B. Port forwarding + # + # NATRule.create() passes openFirewall=True (default for non-VPC), + # so CloudStack automatically creates a TCP/22 FW rule with + # relatedRuleId=pf_rule.id. pf_rule.delete() cascade-removes it. + # Do NOT call _create_firewall_rule_for_ssh() — that would conflict + # with the auto-created rule. + # ============================================================== + self.logger.info("--- Sub-test B: Port forwarding ---") + pf_ip_obj = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=network.id + ) + pf_ip = pf_ip_obj.ipaddress.ipaddress + pf_ip_id = pf_ip_obj.ipaddress.id + + pf_rule = NATRule.create( + self.apiclient, vm, + {"privateport": 22, "publicport": 22, "protocol": "TCP"}, + ipaddressid=pf_ip_id, + networkid=network.id + ) + self.assertIsNotNone(pf_rule) + self.logger.info("PF rule created: %s:22 → VM:22 (FW rule auto-created)", + pf_ip) + + self._assert_vm_ssh_accessible( + pf_ip, 22, + "SSH via PF %s:22 should succeed" % pf_ip) + self.logger.info("Verified: SSH works via port forwarding %s", pf_ip) + + # delete() cascades revokeRelatedFirewallRule() — removes auto FW rule + pf_rule.delete(self.apiclient) + self.logger.info("PF rule deleted on %s", pf_ip) + self._assert_vm_ssh_not_accessible( + pf_ip, 22, + "SSH via %s:22 should fail after PF rule deleted" % pf_ip) + self.logger.info("Verified: SSH fails after PF rule deleted") + pf_ip_obj.delete(self.apiclient) + + # ============================================================== + # C. Load balancer (haproxy) + # + # LoadBalancerRule.create() also uses openFirewall=True by default, + # auto-creating a TCP/22 FW rule. lb_rule.delete() cascades it. + # ============================================================== + self.logger.info("--- Sub-test C: Load balancer ---") + lb_ip_obj = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=network.id + ) + lb_ip = lb_ip_obj.ipaddress.ipaddress + lb_ip_id = lb_ip_obj.ipaddress.id + + lb_rule = LoadBalancerRule.create( + self.apiclient, + {"name": "lb-ssh-%s" % random_gen(), + "alg": "roundrobin", + "privateport": 22, + "publicport": 22}, + ipaddressid=lb_ip_id, + accountid=account.name, + networkid=network.id, + domainid=account.domainid + ) + self.assertIsNotNone(lb_rule) + lb_rule.assign(self.apiclient, vms=[vm]) + self.logger.info("LB rule created, VM assigned: %s:22 (FW rule auto-created)", + lb_ip) + + self._assert_vm_ssh_accessible( + lb_ip, 22, + "SSH via LB %s:22 should succeed (haproxy required on KVM hosts)" + % lb_ip) + self.logger.info("Verified: SSH works via haproxy LB %s", lb_ip) + + lb_rule.remove(self.apiclient, vms=[vm]) + lb_rule.delete(self.apiclient) + lb_ip_obj.delete(self.apiclient) + self.logger.info("LB rule deleted") + + # ============================================================== + # D. Network restart (cleanup=True) + # + # NATRule.create() auto-creates the TCP/22 FW rule. + # After network restart both the PF rule and the FW rule must be + # re-applied by the extension, so SSH should work again. + # ============================================================== + self.logger.info("--- Sub-test D: Network restart ---") + rst_ip_obj = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=network.id + ) + rst_ip = rst_ip_obj.ipaddress.ipaddress + rst_ip_id = rst_ip_obj.ipaddress.id + + rst_pf = NATRule.create( + self.apiclient, vm, + {"privateport": 22, "publicport": 22, "protocol": "TCP"}, + ipaddressid=rst_ip_id, + networkid=network.id + ) + self._assert_vm_ssh_accessible( + rst_ip, 22, + "SSH via %s:22 should work before restart" % rst_ip) + self.logger.info("Baseline SSH verified before restart") + + self.logger.info("Restarting network %s (cleanup=True) ...", network.id) + network.restart(self.apiclient, cleanup=True) + self.logger.info("Network restart completed") + + self._assert_vm_ssh_accessible( + rst_ip, 22, + "SSH via %s:22 should work after network restart" % rst_ip) + self.logger.info("Verified: SSH restored after restart") + + rst_pf.delete(self.apiclient) + rst_ip_obj.delete(self.apiclient) + + # ---- Final cleanup ---- + vm.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm] + network.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != network] + self._teardown_extension() + self.logger.info("test_05 PASSED") + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_06_vpc_multi_tier_and_restart(self): + """VPC multi-tier + VPC restart with SSH connectivity verification. + + Creates two VPC tier networks backed by the extension, deploys a + VM in each tier, and verifies SSH access independently. + + Sub-tests in order + ------------------ + A. Baseline connectivity (before VPC restart) + tier-1 PF :22 → VM1 — assert SSH works + tier-2 LB :22 → VM2 — assert SSH works + tier-1 static NAT :22 → VM1 — enable, create FW rule, + assert SSH works + B. VPC restart (cleanup=True) + assert SSH still works via tier-1 PF, tier-2 LB, and + tier-1 static NAT after the namespace is rebuilt + C. Static NAT teardown (after VPC restart) + disable static NAT → assert SSH fails → delete IP + D. Partial delete + delete tier-1 VM + network → assert tier-2 VM still accessible + E. Final delete + delete tier-2 VM + network, VPC, teardown extension + + The VPC tier network offering uses ``useVpc=on`` as required by + CloudStack for VPC-associated tier networks. + """ + self._check_kvm_host_prerequisites(['arping', 'dnsmasq', 'haproxy']) + # ---- Setup: extension + NSP only (no isolated offering for VPC tests) ---- + _nw_offering, ext_name = self._setup_extension_nsp_offering( + "extnet-vpc", for_vpc=True) + + # ---- VPC tier network offering (useVpc=on) ---- + vpc_tier_svc = VPC_NETWORK_SERVICES + _tier_prov = {s.strip(): ext_name for s in vpc_tier_svc.split(',')} + vpc_tier_offering = NetworkOffering.create(self.apiclient, { + "name": "ExtNet-VPCTier-%s" % random_gen(), + "displaytext": "ExtNet VPC tier offering", + "guestiptype": "Isolated", + "traffictype": "GUEST", + "availability": "Optional", + "useVpc": "on", + "supportedservices": vpc_tier_svc, + "serviceProviderList": _tier_prov, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + }, + }) + self.cleanup.append(vpc_tier_offering) + vpc_tier_offering.update(self.apiclient, state='Enabled') + self.logger.info("VPC tier offering '%s' enabled", vpc_tier_offering.name) + + # ---- VPC offering ---- + vpc_svc = VPC_NETWORK_SERVICES + _vpc_prov = {s.strip(): ext_name for s in vpc_svc.split(',')} + vpc_offering = VpcOffering.create(self.apiclient, { + "name": "ExtNet-VPC-%s" % random_gen(), + "displaytext": "ExtNet VPC offering", + "supportedservices": vpc_svc, + "serviceProviderList": _vpc_prov, + }) + self.cleanup.append(vpc_offering) + vpc_offering.update(self.apiclient, state='Enabled') + self.logger.info("VPC offering '%s' enabled", vpc_offering.name) + + # ---- Account ---- + suffix = random_gen() + account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup.append(account) + account_keypair = self._create_account_keypair(account, suffix) + + # ---- VPC ---- + vpc = VPC.create( + self.apiclient, + {"name": "extnet-vpc-%s" % suffix, + "displaytext": "ExtNet VPC %s" % suffix, + "cidr": "10.1.0.0/16"}, + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=account.name, + domainid=account.domainid + ) + self.cleanup.insert(0, vpc) + self.logger.info("VPC created: %s (%s)", vpc.name, vpc.id) + + # ---- Tier 1 ---- + tier1 = Network.create( + self.apiclient, + {"name": "tier1-%s" % suffix, + "displaytext": "Tier 1 %s" % suffix}, + accountid=account.name, + domainid=account.domainid, + networkofferingid=vpc_tier_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.1.1.1", + netmask="255.255.255.0" + ) + self.cleanup.insert(0, tier1) + self.logger.info("Tier 1 created: %s (%s)", tier1.name, tier1.id) + + # ---- Tier 2 ---- + tier2 = Network.create( + self.apiclient, + {"name": "tier2-%s" % suffix, + "displaytext": "Tier 2 %s" % suffix}, + accountid=account.name, + domainid=account.domainid, + networkofferingid=vpc_tier_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.1.2.1", + netmask="255.255.255.0" + ) + self.cleanup.insert(0, tier2) + self.logger.info("Tier 2 created: %s (%s)", tier2.name, tier2.id) + + svc_offering = ServiceOffering.list(self.apiclient, issystem=False)[0] + + # ---- VM in tier 1 ---- + vm1_cfg = {"displayname": "vm1-%s" % suffix, + "name": "vm1-%s" % suffix, + "zoneid": self.zone.id} + vm1_kw = dict(accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[tier1.id]) + if account_keypair: + vm1_kw["keypair"] = account_keypair.name + vm1 = VirtualMachine.create(self.apiclient, vm1_cfg, **vm1_kw) + self.cleanup.insert(0, vm1) + self.logger.info("VM1 deployed in tier 1: %s (%s)", vm1.name, vm1.id) + + # ---- VM in tier 2 ---- + vm2_cfg = {"displayname": "vm2-%s" % suffix, + "name": "vm2-%s" % suffix, + "zoneid": self.zone.id} + vm2_kw = dict(accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[tier2.id]) + if account_keypair: + vm2_kw["keypair"] = account_keypair.name + vm2 = VirtualMachine.create(self.apiclient, vm2_cfg, **vm2_kw) + self.cleanup.insert(0, vm2) + self.logger.info("VM2 deployed in tier 2: %s (%s)", vm2.name, vm2.id) + + # ---- Tier 1: PF rule ---- + pf_ip1 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier1.id, + vpcid=vpc.id + ) + pf_rule1 = NATRule.create( + self.apiclient, vm1, + {"privateport": 22, "publicport": 22, "protocol": "TCP"}, + ipaddressid=pf_ip1.ipaddress.id, + networkid=tier1.id + ) + tier1_pf_ip = pf_ip1.ipaddress.ipaddress + self.logger.info("Tier 1 PF: %s:22 → VM1:22", tier1_pf_ip) + + # ---- Tier 2: LB rule ---- + lb_ip2 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier2.id, + vpcid=vpc.id + ) + lb_rule2 = LoadBalancerRule.create( + self.apiclient, + {"name": "vpc-lb-ssh-%s" % random_gen(), + "alg": "roundrobin", + "privateport": 22, + "publicport": 22}, + ipaddressid=lb_ip2.ipaddress.id, + accountid=account.name, + networkid=tier2.id, + domainid=account.domainid + ) + self.assertIsNotNone(lb_rule2) + lb_rule2.assign(self.apiclient, vms=[vm2]) + tier2_lb_ip = lb_ip2.ipaddress.ipaddress + self.logger.info("Tier 2 LB: %s:22 → VM2:22", tier2_lb_ip) + + # ---- Tier 1: Static NAT (allocated BEFORE restart) ---- + snat_ip1_obj = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier1.id, + vpcid=vpc.id + ) + snat_ip1 = snat_ip1_obj.ipaddress.ipaddress + snat_ip1_id = snat_ip1_obj.ipaddress.id + StaticNATRule.enable( + self.apiclient, + ipaddressid=snat_ip1_id, + virtualmachineid=vm1.id, + networkid=tier1.id + ) + self.logger.info("Static NAT enabled on tier-1: %s → VM1", snat_ip1) + + # ============================================================== + # A. Baseline connectivity — BEFORE VPC restart + # ============================================================== + self.logger.info("--- Sub-test A: Baseline connectivity (before restart) ---") + + self._assert_vm_ssh_accessible( + tier1_pf_ip, 22, + "SSH to tier-1 VM via PF (%s) should succeed before restart" + % tier1_pf_ip) + self.logger.info("Verified: SSH to tier-1 VM works via PF (before restart)") + + self._assert_vm_ssh_accessible( + tier2_lb_ip, 22, + "SSH to tier-2 VM via LB (%s) should succeed before restart" + % tier2_lb_ip) + self.logger.info("Verified: SSH to tier-2 VM works via LB (before restart)") + + self._assert_vm_ssh_accessible( + snat_ip1, 22, + "SSH to tier-1 VM via static NAT (%s) should succeed before restart" + % snat_ip1) + self.logger.info("Verified: SSH to tier-1 VM works via static NAT (before restart)") + + # ============================================================== + # B. VPC restart (cleanup=True) + # Re-verify all three access methods after the namespace is rebuilt. + # ============================================================== + self.logger.info("--- Sub-test B: VPC restart (cleanup=True) ---") + self.logger.info("Restarting VPC %s (cleanup=True) ...", vpc.id) + vpc.restart(self.apiclient, cleanup=True) + self.logger.info("VPC restart completed") + + self._assert_vm_ssh_accessible( + tier1_pf_ip, 22, + "SSH to tier-1 VM via PF must work after VPC restart") + self.logger.info("Verified: SSH to tier-1 VM works via PF (after restart)") + + self._assert_vm_ssh_accessible( + tier2_lb_ip, 22, + "SSH to tier-2 VM via LB must work after VPC restart") + self.logger.info("Verified: SSH to tier-2 VM works via LB (after restart)") + + self._assert_vm_ssh_accessible( + snat_ip1, 22, + "SSH to tier-1 VM via static NAT must work after VPC restart") + self.logger.info("Verified: SSH to tier-1 VM works via static NAT (after restart)") + + # ============================================================== + # C. Disable static NAT (after VPC restart) + # ============================================================== + self.logger.info("--- Sub-test C: Disable static NAT (after restart) ---") + StaticNATRule.disable(self.apiclient, ipaddressid=snat_ip1_id) + self.logger.info("Static NAT disabled on tier-1 %s", snat_ip1) + self._assert_vm_ssh_not_accessible( + snat_ip1, 22, + "SSH via tier-1 %s:22 should fail after static NAT disabled" % snat_ip1) + self.logger.info("Verified: SSH fails after static NAT disabled") + snat_ip1_obj.delete(self.apiclient) + + # ============================================================== + # D. Partial delete: tier-1 — tier-2 must remain accessible + # ============================================================== + self.logger.info("--- Sub-test D: Delete tier-1, verify tier-2 intact ---") + pf_rule1.delete(self.apiclient) + pf_ip1.delete(self.apiclient) + vm1.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm1] + tier1.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != tier1] + self.logger.info("Tier 1 VM + network deleted") + + # Tier 2 must remain accessible — cmd_destroy() on tier-1 must not + # delete tier-2's public veth (fixed via .tier ownership tracking). + self._assert_vm_ssh_accessible( + tier2_lb_ip, 22, + "SSH to tier-2 VM via LB must still work after tier-1 deleted") + self.logger.info("Verified: tier-2 VM still accessible via LB after tier-1 deleted") + + # ============================================================== + # E. Final delete: tier-2, VPC + # ============================================================== + lb_rule2.remove(self.apiclient, vms=[vm2]) + lb_rule2.delete(self.apiclient) + lb_ip2.delete(self.apiclient) + vm2.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm2] + tier2.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != tier2] + self.logger.info("Tier 2 VM + network deleted") + + vpc.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != vpc] + + self._teardown_extension() + self.logger.info("test_06 PASSED") + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_07_vpc_network_acl(self): + """VPC Network ACL testing with multiple tiers and traffic rules. + + Creates two VPC tiers with distinct network ACL lists and verifies that + ACL rules are correctly applied: + - Inbound rules from public network (via port forwarding) + - Egress rules between VPC tiers (via ping) + + Sub-tests in order + ------------------ + A. Setup: Create two tiers with different ACL lists + tier1 (acl1): Allow ICMP from anywhere, Deny SSH + tier2 (acl2): Allow ICMP and SSH from anywhere + B. Deploy VMs and verify ACLs block SSH to tier1, allow ICMP to both tiers + C. Test inter-tier ICMP communication between VMs + D. Verify SSH only works on tier2 (where ACL permits it) + E. Cleanup + + The test uses ICMP (ping) to verify inter-tier connectivity and SSH + attempts to verify ACL rules are enforced on ingress traffic. + """ + self._check_kvm_host_prerequisites(['arping', 'dnsmasq', 'haproxy']) + + # ---- Setup: extension + NSP (supporting NetworkACL) ---- + _nw_offering, ext_name = self._setup_extension_nsp_offering( + "extnet-acl", for_vpc=True) + + # ---- VPC tier network offering (useVpc=on, with NetworkACL support) ---- + vpc_tier_svc = VPC_NETWORK_SERVICES + _tier_prov = {s.strip(): ext_name for s in vpc_tier_svc.split(',')} + vpc_tier_offering = NetworkOffering.create(self.apiclient, { + "name": "ExtNet-VPCTier-ACL-%s" % random_gen(), + "displaytext": "ExtNet VPC tier offering with ACL", + "guestiptype": "Isolated", + "traffictype": "GUEST", + "availability": "Optional", + "useVpc": "on", + "supportedservices": vpc_tier_svc, + "serviceProviderList": _tier_prov, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + }, + }) + self.cleanup.append(vpc_tier_offering) + vpc_tier_offering.update(self.apiclient, state='Enabled') + self.logger.info("VPC tier offering '%s' enabled", vpc_tier_offering.name) + + # ---- VPC offering ---- + vpc_svc = VPC_NETWORK_SERVICES + _vpc_prov = {s.strip(): ext_name for s in vpc_svc.split(',')} + vpc_offering = VpcOffering.create(self.apiclient, { + "name": "ExtNet-VPC-ACL-%s" % random_gen(), + "displaytext": "ExtNet VPC offering with ACL", + "supportedservices": vpc_svc, + "serviceProviderList": _vpc_prov, + }) + self.cleanup.append(vpc_offering) + vpc_offering.update(self.apiclient, state='Enabled') + self.logger.info("VPC offering '%s' enabled", vpc_offering.name) + + # ---- Account ---- + suffix = random_gen() + account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup.append(account) + account_keypair = self._create_account_keypair(account, suffix) + + # ---- VPC ---- + vpc = VPC.create( + self.apiclient, + {"name": "extnet-vpc-acl-%s" % suffix, + "displaytext": "ExtNet VPC ACL %s" % suffix, + "cidr": "10.2.0.0/16"}, + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=account.name, + domainid=account.domainid + ) + self.cleanup.insert(0, vpc) + self.logger.info("VPC created: %s (%s)", vpc.name, vpc.id) + + # ---- Network ACL Lists ---- + # ACL1: Restrict to ICMP only (deny SSH) + acl1 = NetworkACLList.create( + self.apiclient, + {"name": "acl1-icmp-only-%s" % suffix, + "description": "ACL1 for tier1 - ICMP only"}, + vpcid=vpc.id + ) + self.cleanup.insert(0, acl1) + self.logger.info("ACL1 created: %s (ICMP only)", acl1.id) + + # ACL2: Allow ICMP and SSH + acl2 = NetworkACLList.create( + self.apiclient, + {"name": "acl2-icmp-ssh-%s" % suffix, + "description": "ACL2 for tier2 - ICMP and SSH allowed"}, + vpcid=vpc.id + ) + self.cleanup.insert(0, acl2) + self.logger.info("ACL2 created: %s (ICMP and SSH)", acl2.id) + + # ---- Tier 1 with ACL1 (ICMP only) ---- + tier1 = Network.create( + self.apiclient, + {"name": "tier1-acl-%s" % suffix, + "displaytext": "Tier 1 ACL %s" % suffix}, + accountid=account.name, + domainid=account.domainid, + networkofferingid=vpc_tier_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.2.1.1", + netmask="255.255.255.0" + ) + self.cleanup.insert(0, tier1) + self.logger.info("Tier 1 created: %s (%s)", tier1.name, tier1.id) + + # ---- Tier 2 with ACL2 (ICMP + SSH) ---- + tier2 = Network.create( + self.apiclient, + {"name": "tier2-acl-%s" % suffix, + "displaytext": "Tier 2 ACL %s" % suffix}, + accountid=account.name, + domainid=account.domainid, + networkofferingid=vpc_tier_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.2.2.1", + netmask="255.255.255.0" + ) + self.cleanup.insert(0, tier2) + self.logger.info("Tier 2 created: %s (%s)", tier2.name, tier2.id) + + svc_offering = ServiceOffering.list(self.apiclient, issystem=False)[0] + + # ---- VM in tier 1 ---- + vm1_cfg = {"displayname": "vm1-acl-%s" % suffix, + "name": "vm1-acl-%s" % suffix, + "zoneid": self.zone.id} + vm1_kw = dict(accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[tier1.id]) + if account_keypair: + vm1_kw["keypair"] = account_keypair.name + vm1 = VirtualMachine.create(self.apiclient, vm1_cfg, **vm1_kw) + self.cleanup.insert(0, vm1) + self.logger.info("VM1 deployed in tier 1: %s (%s)", vm1.name, vm1.id) + + # ---- VM in tier 2 ---- + vm2_cfg = {"displayname": "vm2-acl-%s" % suffix, + "name": "vm2-acl-%s" % suffix, + "zoneid": self.zone.id} + vm2_kw = dict(accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[tier2.id]) + if account_keypair: + vm2_kw["keypair"] = account_keypair.name + vm2 = VirtualMachine.create(self.apiclient, vm2_cfg, **vm2_kw) + self.cleanup.insert(0, vm2) + self.logger.info("VM2 deployed in tier 2: %s (%s)", vm2.name, vm2.id) + + # Get VM IPs for later use + vm1_networks = VirtualMachine.list(self.apiclient, id=vm1.id)[0].nic + vm1_ip = None + for nic in vm1_networks: + if nic.networkid == tier1.id: + vm1_ip = nic.ipaddress + self.assertIsNotNone(vm1_ip, "VM1 should have IP in tier1") + self.logger.info("VM1 IP in tier1: %s", vm1_ip) + + vm2_networks = VirtualMachine.list(self.apiclient, id=vm2.id)[0].nic + vm2_ip = None + for nic in vm2_networks: + if nic.networkid == tier2.id: + vm2_ip = nic.ipaddress + self.assertIsNotNone(vm2_ip, "VM2 should have IP in tier2") + self.logger.info("VM2 IP in tier2: %s", vm2_ip) + + # ============================================================== + # A. Setup ACL rules + # ============================================================== + self.logger.info("--- Sub-test A: Setting up ACL rules ---") + + # ACL1 rules: ICMP allowed, SSH denied (ingress), ICMP allowed (egress) + # Rule numbers must be unique per ACL list (across ingress+egress). + # Ingress rule: Allow ICMP + NetworkACL.create( + self.apiclient, + {"protocol": "ICMP", "icmptype": -1, "icmpcode": -1, + "traffictype": "Ingress", "aclid": acl1.id, + "cidrlist": ["0.0.0.0/0"], "action": "Allow", "number": 10}, + networkid=tier1.id + ) + self.logger.info("ACL1 Ingress rule: ICMP Allow") + + # Ingress rule: Deny SSH + NetworkACL.create( + self.apiclient, + {"protocol": "TCP", "startport": 22, "endport": 22, + "traffictype": "Ingress", "aclid": acl1.id, + "cidrlist": ["0.0.0.0/0"], "action": "Deny", "number": 20}, + networkid=tier1.id + ) + self.logger.info("ACL1 Ingress rule: SSH Deny") + + # Egress rule: Allow all + NetworkACL.create( + self.apiclient, + {"protocol": "All", "traffictype": "Egress", "aclid": acl1.id, + "cidrlist": ["0.0.0.0/0"], "action": "Allow", "number": 30}, + networkid=tier1.id + ) + self.logger.info("ACL1 Egress rule: All Allow") + + # ACL2 rules: ICMP and SSH allowed (ingress), All allowed (egress) + # Ingress rule: Allow ICMP + NetworkACL.create( + self.apiclient, + {"protocol": "ICMP", "icmptype": -1, "icmpcode": -1, + "traffictype": "Ingress", "aclid": acl2.id, + "cidrlist": ["0.0.0.0/0"], "action": "Allow", "number": 10}, + networkid=tier2.id + ) + self.logger.info("ACL2 Ingress rule: ICMP Allow") + + # Ingress rule: Allow SSH + NetworkACL.create( + self.apiclient, + {"protocol": "TCP", "startport": 22, "endport": 22, + "traffictype": "Ingress", "aclid": acl2.id, + "cidrlist": ["0.0.0.0/0"], "action": "Allow", "number": 20}, + networkid=tier2.id + ) + self.logger.info("ACL2 Ingress rule: SSH Allow") + + # Egress rule: Allow all + NetworkACL.create( + self.apiclient, + {"protocol": "All", "traffictype": "Egress", "aclid": acl2.id, + "cidrlist": ["0.0.0.0/0"], "action": "Allow", "number": 30}, + networkid=tier2.id + ) + self.logger.info("ACL2 Egress rule: All Allow") + + # Apply the ACL lists to the tier networks + tier1.replaceACLList(self.apiclient, acl1.id) + self.logger.info("Applied ACL1 (deny SSH) to tier1") + tier2.replaceACLList(self.apiclient, acl2.id) + self.logger.info("Applied ACL2 (allow SSH) to tier2") + + # ============================================================== + # B. Test Public IP access with ACL enforcement (via PF) + # ============================================================== + self.logger.info("--- Sub-test B: Test public IP access with ACLs ---") + + # Create public IP and PF for tier1 (should block SSH due to ACL1) + ip1 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier1.id, + vpcid=vpc.id + ) + tier1_public_ip = ip1.ipaddress.ipaddress + self.logger.info("Tier1 public IP allocated: %s", tier1_public_ip) + + pf_rule1 = NATRule.create( + self.apiclient, vm1, + {"privateport": 22, "publicport": 22, "protocol": "TCP"}, + ipaddressid=ip1.ipaddress.id, + networkid=tier1.id, + vpcid=vpc.id + ) + self.assertIsNotNone(pf_rule1) + self.logger.info("Tier1 PF rule created: %s:22 → VM1:22", tier1_public_ip) + + # SSH to tier1 should fail due to ACL denying SSH + self._assert_vm_ssh_not_accessible( + tier1_public_ip, 22, + "SSH to tier1 %s should FAIL (ACL denies SSH)" % tier1_public_ip) + self.logger.info("Verified: SSH to tier1 correctly blocked by ACL") + + # Create public IP and PF for tier2 (should allow SSH due to ACL2) + ip2 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier2.id, + vpcid=vpc.id + ) + tier2_public_ip = ip2.ipaddress.ipaddress + self.logger.info("Tier2 public IP allocated: %s", tier2_public_ip) + + pf_rule2 = NATRule.create( + self.apiclient, vm2, + {"privateport": 22, "publicport": 22, "protocol": "TCP"}, + ipaddressid=ip2.ipaddress.id, + networkid=tier2.id, + vpcid=vpc.id + ) + self.assertIsNotNone(pf_rule2) + self.logger.info("Tier2 PF rule created: %s:22 → VM2:22", tier2_public_ip) + + # SSH to tier2 should succeed due to ACL allowing SSH + self._assert_vm_ssh_accessible( + tier2_public_ip, 22, + "SSH to tier2 %s should succeed (ACL allows SSH)" % tier2_public_ip) + self.logger.info("Verified: SSH to tier2 correctly allowed by ACL") + + # ============================================================== + # C. Test inter-tier ICMP communication + # ============================================================== + self.logger.info("--- Sub-test C: Test inter-tier ICMP (ping) ---") + + # From VM2 (tier2), ping VM1 (tier1) — should succeed (both allow ICMP egress) + # This tests that the ACL egress rules work and inter-tier routing is OK + try: + ssh_vm2 = SshClient( + tier2_public_ip, 22, "ubuntu", None, + keyPairFiles=self._ssh_private_key_file, + timeout=30, retries=10 + ) + out = ssh_vm2.execute("ping -c 3 %s 2>&1 | tail -5" % vm1_ip) + ping_result = "\n".join(out) + # Check if ping succeeded (look for "3 packets transmitted" or similar) + if "transmitted" in ping_result.lower(): + self.logger.info("Ping from VM2 to VM1 output:\n%s", ping_result) + if "0 received" not in ping_result.lower(): + self.logger.info("Verified: Inter-tier ping succeeded (ICMP egress rule allows it)") + else: + self.logger.warning("Ping packets were transmitted but all lost") + else: + self.logger.warning("Could not determine ping result from: %s", ping_result) + except Exception as e: + self.logger.warning("Could not execute ping test: %s", e) + + # ============================================================== + # D. Additional SSH verification + # ============================================================== + self.logger.info("--- Sub-test D: Additional SSH verification ---") + + # Re-verify tier2 SSH works after ACL rules are fully active + self._assert_vm_ssh_accessible( + tier2_public_ip, 22, + "SSH to tier2 should still work (ACL permits SSH)") + self.logger.info("Verified: SSH to tier2 confirmed working") + + # ============================================================== + # E. Cleanup + # ============================================================== + self.logger.info("--- Sub-test E: Cleanup ---") + pf_rule1.delete(self.apiclient) + ip1.delete(self.apiclient) + pf_rule2.delete(self.apiclient) + ip2.delete(self.apiclient) + + vm1.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm1] + vm2.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm2] + tier1.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != tier1] + tier2.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != tier2] + + vpc.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != vpc and o != acl1 and o != acl2] + + self._teardown_extension() + self.logger.info("test_07 PASSED") + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_08_custom_action_policy_based_routing(self): + """Custom-action smoke test for PBR lifecycle helpers. + + Verifies that network custom actions can create/list/delete: + - routing tables + - routes per table + - policy rules + """ + self._check_kvm_host_prerequisites(['ip', 'arping', 'dnsmasq', 'haproxy']) + + svc = ISOLATED_NETWORK_SERVICES + nw_offering, _ext_name = self._setup_extension_nsp_offering( + "extnet-pbr", supported_services=svc) + _account, network, vm = self._create_account_network_vm( + nw_offering, name_suffix="pbr") + + # Use a unique table name to avoid collisions with stale test state. + table_name = "app-%s" % random.randint(100, 999) + route_cidr = "172.30.%d.0/24" % random.randint(1, 200) + + actions = [] + try: + def _mk_action(name, parameters = []): + a = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + enabled=True, + name=name, + description="PBR smoke: %s" % name, + resourcetype='Network', + parameters=parameters + ) + actions.append(a) + return a + + act_create_table = _mk_action("pbr-create-table", parameters=[ + {"name": "table-id", "type": "STRING", "required": True}, + {"name": "table-name", "type": "STRING", "required": True}, + ]) + act_delete_table = _mk_action("pbr-delete-table", parameters=[ + {"name": "table-name", "type": "STRING", "required": True}, + ]) + act_list_tables = _mk_action("pbr-list-tables") + act_add_route = _mk_action("pbr-add-route", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "route", "type": "STRING", "required": True}, + ]) + act_delete_route = _mk_action("pbr-delete-route", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "route", "type": "STRING", "required": True}, + ]) + act_list_routes = _mk_action("pbr-list-routes", parameters=[ + {"name": "table", "type": "STRING", "required": False}, + ]) + act_add_rule = _mk_action("pbr-add-rule", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "rule", "type": "STRING", "required": True}, + ]) + act_delete_rule = _mk_action("pbr-delete-rule", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "rule", "type": "STRING", "required": True}, + ]) + act_list_rules = _mk_action("pbr-list-rules", parameters=[ + {"name": "table", "type": "STRING", "required": False}, + ]) + + # 1) Create and list routing table + out = self._run_custom_action_logged( + act_create_table, + resourceid=network.id, + parameters=[{"table-id": "100", "table-name": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-create-table should succeed") + + out = self._run_custom_action_logged(act_list_tables, resourceid=network.id) + self.assertTrue(getattr(out, 'success', False), "pbr-list-tables should succeed") + self.assertIn(table_name, self._custom_action_details(out)) + + # 2) Add and list route in table + out = self._run_custom_action_logged( + act_add_route, + resourceid=network.id, + parameters=[{"table": table_name, "route": "blackhole %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-add-route should succeed") + + out = self._run_custom_action_logged( + act_list_routes, + resourceid=network.id, + parameters=[{"table": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-list-routes should succeed") + self.assertIn(route_cidr, self._custom_action_details(out)) + + # 3) Add and list policy rule + out = self._run_custom_action_logged( + act_add_rule, + resourceid=network.id, + parameters=[{"table": table_name, "rule": "to %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-add-rule should succeed") + + out = self._run_custom_action_logged( + act_list_rules, + resourceid=network.id, + parameters=[{"table": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-list-rules should succeed") + self.assertIn(table_name, self._custom_action_details(out)) + + # 4) Delete policy rule, route, and table + out = self._run_custom_action_logged( + act_delete_rule, + resourceid=network.id, + parameters=[{"table": table_name, "rule": "to %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-delete-rule should succeed") + + out = self._run_custom_action_logged( + act_delete_route, + resourceid=network.id, + parameters=[{"table": table_name, "route": "blackhole %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-delete-route should succeed") + + out = self._run_custom_action_logged( + act_delete_table, + resourceid=network.id, + parameters=[{"table-name": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-delete-table should succeed") + + self.logger.info("test_08 PASSED") + finally: + for action in actions: + try: + action.delete(self.apiclient) + except Exception: + pass + try: + vm.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm] + except Exception: + pass + try: + network.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != network] + except Exception: + pass + try: + self._teardown_extension() + except Exception: + pass + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_09_vpc_source_nat_ip_update(self): + """Update VPC source NAT IP and verify old/new source NAT flags flip correctly.""" + self._check_kvm_host_prerequisites(['arping']) + + svc = VPC_NETWORK_SERVICES + _nw_offering, ext_name = self._setup_extension_nsp_offering( + "extnet-vpc-snat-update", for_vpc=True) + + suffix = random_gen() + account = None + vpc = None + tier = None + vm = None + ip1 = None + ip2 = None + pf_rule = None + lb_rule = None + static_nat_enabled = False + try: + # VPC tier network offering (useVpc=on) + _tier_prov = {s.strip(): ext_name for s in svc.split(',')} + vpc_tier_offering = NetworkOffering.create(self.apiclient, { + "name": "ExtNet-VPCTier-SNAT-%s" % random_gen(), + "displaytext": "ExtNet VPC tier offering for source NAT update", + "guestiptype": "Isolated", + "traffictype": "GUEST", + "availability": "Optional", + "useVpc": "on", + "supportedservices": svc, + "serviceProviderList": _tier_prov, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + }, + }) + self.cleanup.append(vpc_tier_offering) + vpc_tier_offering.update(self.apiclient, state='Enabled') + self.logger.info("VPC tier offering '%s' enabled", vpc_tier_offering.name) + + # VPC offering + _vpc_prov = {s.strip(): ext_name for s in svc.split(',')} + vpc_offering = VpcOffering.create(self.apiclient, { + "name": "ExtNet-VPC-SNAT-%s" % random_gen(), + "displaytext": "ExtNet VPC offering for source NAT update", + "supportedservices": svc, + "serviceProviderList": _vpc_prov, + }) + self.cleanup.append(vpc_offering) + vpc_offering.update(self.apiclient, state='Enabled') + self.logger.info("VPC offering '%s' enabled", vpc_offering.name) + + account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup.append(account) + account_keypair = self._create_account_keypair(account, suffix) + + vpc = VPC.create( + self.apiclient, + {"name": "extnet-vpc-snat-%s" % suffix, + "displaytext": "ExtNet VPC SNAT %s" % suffix, + "cidr": "10.3.0.0/16"}, + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=account.name, + domainid=account.domainid + ) + self.cleanup.insert(0, vpc) + self.logger.info("VPC created: %s (%s)", vpc.name, vpc.id) + + tier = Network.create( + self.apiclient, + {"name": "tier-snat-%s" % suffix, + "displaytext": "Tier SNAT %s" % suffix}, + accountid=account.name, + domainid=account.domainid, + networkofferingid=vpc_tier_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.3.1.1", + netmask="255.255.255.0" + ) + self.cleanup.insert(0, tier) + + # Create a vm instance in the tier, so that the vpc and tier are implemented on the backend and have their source NAT IPs allocated. + svc_offering = ServiceOffering.list(self.apiclient, issystem=False)[0] + vm_cfg = {"displayname": "vm-snat-%s" % suffix, + "name": "vm-snat-%s" % suffix, + "zoneid": self.zone.id} + vm_kw = dict(accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[tier.id]) + if account_keypair: + vm_kw["keypair"] = account_keypair.name + vm = VirtualMachine.create(self.apiclient, vm_cfg, **vm_kw) + self.cleanup.insert(0, vm) + + # Wait for the VPC's auto-assigned source NAT IP (CloudStack assigns + # one automatically when the VPC is first used; it is NOT necessarily + # the first manually-allocated IP). + src_before = self._wait_for_vpc_source_nat_ip(vpc.id) + self.assertIsNotNone(src_before, + "A source NAT IP should already exist for the VPC") + src_before_addr = getattr(src_before, 'ipaddress', None) + self.logger.info("Initial source NAT IP: %s", src_before_addr) + + # Allocate ip1 and ip2 as candidates for the updated source NAT. + ip1 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier.id, + vpcid=vpc.id + ) + ip1_addr = ip1.ipaddress.ipaddress + + ip2 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier.id, + vpcid=vpc.id + ) + ip2_addr = ip2.ipaddress.ipaddress + + # Use ip1 for static NAT (SSH/22) and verify ingress connectivity. + StaticNATRule.enable( + self.apiclient, + ip1.ipaddress.id, + vm.id, + networkid=tier.id + ) + static_nat_enabled = True + + # Use ip2 for both PF and LB on different public ports. + pf_port = 2222 + lb_port = 2223 + pf_rule = NATRule.create( + self.apiclient, + vm, + {"privateport": 22, "publicport": pf_port, "protocol": "TCP"}, + ipaddressid=ip2.ipaddress.id, + networkid=tier.id, + vpcid=vpc.id + ) + self.assertIsNotNone(pf_rule, "Port forwarding rule should be created on ip2") + + lb_rule = LoadBalancerRule.create( + self.apiclient, + { + "name": "lb-ssh-%s" % suffix, + "alg": "roundrobin", + "privateport": 22, + "publicport": lb_port, + "protocol": "TCP", + }, + ipaddressid=ip2.ipaddress.id, + accountid=account.name, + domainid=account.domainid, + networkid=tier.id, + vpcid=vpc.id + ) + self.assertIsNotNone(lb_rule, "Load balancer rule should be created on ip2") + lb_rule.assign(self.apiclient, [vm]) + + # Validate all inbound paths before source NAT migration. + self._assert_vm_ssh_accessible( + ip1_addr, 22, + "Static NAT SSH on ip1 should work before source NAT update") + self._assert_vm_ssh_accessible( + ip2_addr, pf_port, + "Port forwarding SSH on ip2 should work before source NAT update") + self._assert_vm_ssh_accessible( + ip2_addr, lb_port, + "Load balancer SSH on ip2 should work before source NAT update") + + ip3 = PublicIPAddress.create( + self.apiclient, + accountid=account.name, + zoneid=self.zone.id, + domainid=account.domainid, + networkid=tier.id, + vpcid=vpc.id + ) + ip3_addr = ip3.ipaddress.ipaddress + + self.logger.info("Updating VPC source NAT IP to %s", ip3_addr) + update_resp = vpc.update(self.apiclient, sourcenatipaddress=ip3_addr) + self.assertIsNotNone(update_resp, + "updateVPC should return a response") + + src_after = self._wait_for_vpc_source_nat_ip(vpc.id, expected_ip=ip3_addr) + self.assertIsNotNone(src_after, + "Updated source NAT IP should be %s" % ip3_addr) + + all_vpc_ips = self._list_vpc_public_ips(vpc.id) + self.assertEqual(len(all_vpc_ips), 4, + "VPC should have four public IPs") + + by_addr = {getattr(x, 'ipaddress', None): x for x in all_vpc_ips} + self.assertIn(src_before_addr, by_addr, + "Original source NAT IP must remain allocated to VPC") + self.assertIn(ip1_addr, by_addr, "Static NAT IP must remain allocated to VPC") + self.assertIn(ip2_addr, by_addr, "New source NAT IP must remain allocated to VPC") + + src_ips = [x for x in all_vpc_ips if getattr(x, 'issourcenat', False)] + self.assertEqual(1, len(src_ips), + "Exactly one VPC public IP must be marked source NAT") + self.assertEqual(ip3_addr, getattr(src_ips[0], 'ipaddress', None), + "Source NAT IP should switch to the requested IP") + self.assertFalse(getattr(by_addr[src_before_addr], 'issourcenat', False), + "Original source NAT IP must be unset after update") + self.assertTrue(getattr(by_addr[ip3_addr], 'issourcenat', False), + "Requested source NAT IP must be marked source NAT") + + # Validate all inbound paths still work after source NAT migration. + self._assert_vm_ssh_accessible( + ip1_addr, 22, + "Static NAT SSH on ip1 should work after source NAT update") + self._assert_vm_ssh_accessible( + ip2_addr, pf_port, + "Port forwarding SSH on ip2 should work after source NAT update") + self._assert_vm_ssh_accessible( + ip2_addr, lb_port, + "Load balancer SSH on ip2 should work after source NAT update") + + self.logger.info("test_09 PASSED") + finally: + try: + if lb_rule and vm: + lb_rule.remove(self.apiclient, [vm]) + except Exception: + pass + try: + if lb_rule: + lb_rule.delete(self.apiclient) + except Exception: + pass + try: + if pf_rule: + pf_rule.delete(self.apiclient) + except Exception: + pass + try: + if static_nat_enabled and ip1: + StaticNATRule.disable(self.apiclient, ip1.ipaddress.id) + except Exception: + pass + try: + if ip1: + ip1.delete(self.apiclient) + except Exception: + pass + try: + if ip2: + ip2.delete(self.apiclient) + except Exception: + pass + try: + if vm: + vm.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm] + except Exception: + pass + try: + if tier: + tier.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != tier] + except Exception: + pass + try: + if vpc: + vpc.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != vpc] + except Exception: + pass + try: + self._teardown_extension() + except Exception: + pass + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_10_vpc_custom_action_policy_based_routing(self): + """Custom-action smoke test for PBR lifecycle helpers on a VPC. + + Same as test_08 but exercised against a VPC instead of + an isolated network. Verifies that network custom actions work + correctly in a VPC context: + - routing tables + - routes per table + - policy rules + """ + self._check_kvm_host_prerequisites(['ip', 'arping', 'dnsmasq', 'haproxy']) + + svc = VPC_NETWORK_SERVICES + _nw_offering, ext_name = self._setup_extension_nsp_offering( + "extnet-vpc-pbr", for_vpc=True) + + # ---- VPC tier network offering (useVpc=on) ---- + _tier_prov = {s.strip(): ext_name for s in svc.split(',')} + vpc_tier_offering = NetworkOffering.create(self.apiclient, { + "name": "ExtNet-VPCTier-PBR-%s" % random_gen(), + "displaytext": "ExtNet VPC tier offering for PBR", + "guestiptype": "Isolated", + "traffictype": "GUEST", + "availability": "Optional", + "useVpc": "on", + "supportedservices": svc, + "serviceProviderList": _tier_prov, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + }, + }) + self.cleanup.append(vpc_tier_offering) + vpc_tier_offering.update(self.apiclient, state='Enabled') + + # ---- VPC offering ---- + _vpc_prov = {s.strip(): ext_name for s in svc.split(',')} + vpc_offering = VpcOffering.create(self.apiclient, { + "name": "ExtNet-VPC-PBR-%s" % random_gen(), + "displaytext": "ExtNet VPC offering for PBR", + "supportedservices": svc, + "serviceProviderList": _vpc_prov, + }) + self.cleanup.append(vpc_offering) + vpc_offering.update(self.apiclient, state='Enabled') + + suffix = random_gen() + account = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=self.domain.id + ) + self.cleanup.append(account) + + vpc = VPC.create( + self.apiclient, + {"name": "extnet-vpc-pbr-%s" % suffix, + "displaytext": "ExtNet VPC PBR %s" % suffix, + "cidr": "10.1.0.0/16"}, + vpcofferingid=vpc_offering.id, + zoneid=self.zone.id, + account=account.name, + domainid=account.domainid + ) + self.cleanup.insert(0, vpc) + + tier = Network.create( + self.apiclient, + {"name": "tier-pbr-%s" % suffix, + "displaytext": "Tier PBR %s" % suffix}, + accountid=account.name, + domainid=account.domainid, + networkofferingid=vpc_tier_offering.id, + zoneid=self.zone.id, + vpcid=vpc.id, + gateway="10.1.1.1", + netmask="255.255.255.0" + ) + self.cleanup.insert(0, tier) + + svc_offering = ServiceOffering.list(self.apiclient, issystem=False)[0] + vm = VirtualMachine.create( + self.apiclient, + {"displayname": "vm-pbr-%s" % suffix, + "name": "vm-pbr-%s" % suffix, + "zoneid": self.zone.id}, + accountid=account.name, + domainid=account.domainid, + serviceofferingid=svc_offering.id, + templateid=self.template.id, + networkids=[tier.id] + ) + self.cleanup.insert(0, vm) + + table_name = "app-%s" % random.randint(100, 999) + route_cidr = "172.30.%d.0/24" % random.randint(1, 200) + + actions = [] + try: + def _mk_action(name, parameters=[]): + a = ExtensionCustomAction.create( + self.apiclient, + extensionid=self.extension.id, + enabled=True, + name=name, + description="VPC PBR smoke: %s" % name, + resourcetype='Vpc', + parameters=parameters + ) + actions.append(a) + return a + + act_create_table = _mk_action("pbr-create-table", parameters=[ + {"name": "table-id", "type": "STRING", "required": True}, + {"name": "table-name", "type": "STRING", "required": True}, + ]) + act_delete_table = _mk_action("pbr-delete-table", parameters=[ + {"name": "table-name", "type": "STRING", "required": True}, + ]) + act_list_tables = _mk_action("pbr-list-tables") + act_add_route = _mk_action("pbr-add-route", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "route", "type": "STRING", "required": True}, + ]) + act_delete_route = _mk_action("pbr-delete-route", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "route", "type": "STRING", "required": True}, + ]) + act_list_routes = _mk_action("pbr-list-routes", parameters=[ + {"name": "table", "type": "STRING", "required": False}, + ]) + act_add_rule = _mk_action("pbr-add-rule", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "rule", "type": "STRING", "required": True}, + ]) + act_delete_rule = _mk_action("pbr-delete-rule", parameters=[ + {"name": "table", "type": "STRING", "required": True}, + {"name": "rule", "type": "STRING", "required": True}, + ]) + act_list_rules = _mk_action("pbr-list-rules", parameters=[ + {"name": "table", "type": "STRING", "required": False}, + ]) + + # 1) Create and list routing table + out = self._run_custom_action_logged( + act_create_table, + resourceid=vpc.id, + parameters=[{"table-id": "100", "table-name": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-create-table should succeed") + + out = self._run_custom_action_logged(act_list_tables, resourceid=vpc.id) + self.assertTrue(getattr(out, 'success', False), "pbr-list-tables should succeed") + self.assertIn(table_name, self._custom_action_details(out)) + + # 2) Add and list route in table + out = self._run_custom_action_logged( + act_add_route, + resourceid=vpc.id, + parameters=[{"table": table_name, "route": "blackhole %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-add-route should succeed") + + out = self._run_custom_action_logged( + act_list_routes, + resourceid=vpc.id, + parameters=[{"table": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-list-routes should succeed") + self.assertIn(route_cidr, self._custom_action_details(out)) + + # 3) Add and list policy rule + out = self._run_custom_action_logged( + act_add_rule, + resourceid=vpc.id, + parameters=[{"table": table_name, "rule": "to %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-add-rule should succeed") + + out = self._run_custom_action_logged( + act_list_rules, + resourceid=vpc.id, + parameters=[{"table": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-list-rules should succeed") + self.assertIn(table_name, self._custom_action_details(out)) + + # 4) Delete policy rule, route, and table + out = self._run_custom_action_logged( + act_delete_rule, + resourceid=vpc.id, + parameters=[{"table": table_name, "rule": "to %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-delete-rule should succeed") + + out = self._run_custom_action_logged( + act_delete_route, + resourceid=vpc.id, + parameters=[{"table": table_name, "route": "blackhole %s" % route_cidr}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-delete-route should succeed") + + out = self._run_custom_action_logged( + act_delete_table, + resourceid=vpc.id, + parameters=[{"table-name": table_name}], + ) + self.assertTrue(getattr(out, 'success', False), "pbr-delete-table should succeed") + + self.logger.info("test_10 PASSED") + finally: + for action in actions: + try: + action.delete(self.apiclient) + except Exception: + pass + try: + vm.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm] + except Exception: + pass + try: + tier.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != tier] + except Exception: + pass + try: + vpc.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != vpc] + except Exception: + pass + try: + self._teardown_extension() + except Exception: + pass + + # ------------------------------------------------------------------ + # IPv6 helper: ensure a guest IPv6 prefix exists for the zone + # ------------------------------------------------------------------ + + def _ensure_guest_ipv6_prefix(self): + """Return an existing or newly created guest IPv6 prefix for the zone. + + Uses ``services["guestip6prefix"]["prefix"]`` when available; falls + back to ``fd17:ac56:1234:1000::/52``. Returns ``(prefix_obj, created)`` + where *created* is True when this call created the prefix (the caller + is responsible for deleting it on teardown). + """ + cmd = listGuestNetworkIpv6Prefixes.listGuestNetworkIpv6PrefixesCmd() + cmd.zoneid = self.zone.id + existing = self.apiclient.listGuestNetworkIpv6Prefixes(cmd) + if isinstance(existing, list) and existing: + self.logger.info("Found existing guest IPv6 prefix: %s", + existing[0].prefix) + return existing[0], False + + prefix_str = (self.services + .get("guestip6prefix", {}) + .get("prefix", "fd17:ac56:1234:1000::/52")) + cmd = createGuestNetworkIpv6Prefix.createGuestNetworkIpv6PrefixCmd() + cmd.zoneid = self.zone.id + cmd.prefix = prefix_str + prefix_obj = self.apiclient.createGuestNetworkIpv6Prefix(cmd) + self.logger.info("Created guest IPv6 prefix: %s", prefix_str) + return prefix_obj, True + + # ------------------------------------------------------------------ + # IPv6 helper: ensure a public IPv6 IP range exists with a VLAN + # ------------------------------------------------------------------ + + def _ensure_public_ipv6_range(self): + """Return an existing or newly created public IPv6 IP range for the zone. + + CloudStack requires a public IP range that has both IPv4 and IPv6 + entries sharing the same VLAN for dualstack isolated networks to work. + This helper: + 1. Returns immediately if an existing range already has ``ip6cidr`` + and ``ip6gateway`` set. + 2. Otherwise extracts the VLAN from the first IPv4 public range and + creates a new IPv6 range using ``services["publicip6range"]``. + + Returns ``(range_obj, created)`` where *created* is True when this call + created the range (the caller is responsible for deleting it on + teardown). + """ + ip_ranges = PublicIpRange.list(self.apiclient, zoneid=self.zone.id) + ipv4_range_vlan = None + if isinstance(ip_ranges, list): + for ip_range in ip_ranges: + if getattr(ip_range, 'ip6cidr', None) and \ + getattr(ip_range, 'ip6gateway', None): + self.logger.info( + "Found existing public IPv6 range: cidr=%s", + ip_range.ip6cidr) + return ip_range, False + if getattr(ip_range, 'netmask', None) and \ + getattr(ip_range, 'gateway', None): + vlan = getattr(ip_range, 'vlan', None) or '' + if ipv4_range_vlan is None and vlan.startswith("vlan://"): + vlan_id = vlan.replace("vlan://", "") + if vlan_id == "untagged": + ipv4_range_vlan = None + else: + try: + ipv4_range_vlan = int(vlan_id) + except ValueError: + ipv4_range_vlan = None + + ip6range_services = dict(self.services.get("publicip6range", {})) + ip6range_services["zoneid"] = self.zone.id + ip6range_services["vlan"] = ipv4_range_vlan + ip6range_obj = PublicIpRange.create(self.apiclient, ip6range_services) + self.logger.info( + "Created public IPv6 range: cidr=%s vlan=%s", + ip6range_services.get("ip6cidr"), ipv4_range_vlan) + return ip6range_obj, True + + # ------------------------------------------------------------------ + # Test 11: IPv6 dualstack isolated network + # ------------------------------------------------------------------ + + @attr(tags=["advanced", "smoke"], required_hardware="true") + def test_11_ipv6_dualstack_isolated_network(self): + """IPv6 dualstack isolated network: namespace address and radvd. + + Creates an isolated network with ``internetprotocol=dualstack`` backed + by the network-namespace extension and verifies end-to-end IPv6 + provisioning: + + A. API attribute verification + network receives ``ip6cidr`` and ``ip6gateway`` + VM NIC receives ``ip6address`` (EUI-64 via SLAAC) + B. KVM namespace verification + IPv6 gateway address is present on the guest veth inside the + namespace + radvd is running inside the namespace with a valid config + (``AdvSendAdvert on``, ``AdvAutonomous on``, correct prefix) + + Prerequisites + ------------- + * KVM hosts reachable and have ``dnsmasq``, ``arping``, ``radvd`` + installed. + * Zone has (or can have) a guest IPv6 prefix configured. + * Global setting ``ipv6.offering.enabled`` can be set to ``true``. + """ + self._check_kvm_host_prerequisites(['arping', 'dnsmasq', 'radvd']) + + # ---- Preserve and enable ipv6.offering.enabled ---- + initial_ipv6_cfg = None + try: + cfg_list = Configurations.list(self.apiclient, + name="ipv6.offering.enabled") + if cfg_list: + initial_ipv6_cfg = cfg_list[0].value + Configurations.update(self.apiclient, "ipv6.offering.enabled", + "true") + self.logger.info("ipv6.offering.enabled set to true " + "(was: %s)", initial_ipv6_cfg) + except Exception as e: + self.skipTest("Cannot enable ipv6.offering.enabled: %s" % e) + + # ---- Ensure a guest IPv6 prefix exists ---- + ip6prefix_obj = None + ip6prefix_created = False + try: + ip6prefix_obj, ip6prefix_created = self._ensure_guest_ipv6_prefix() + except Exception as e: + if initial_ipv6_cfg is not None: + try: + Configurations.update(self.apiclient, + "ipv6.offering.enabled", + initial_ipv6_cfg) + except Exception: + pass + self.skipTest("Cannot set up guest IPv6 prefix: %s" % e) + + # ---- Ensure a public IPv6 range exists (same VLAN as IPv4) ---- + ip6pubrange_obj = None + ip6pubrange_created = False + try: + ip6pubrange_obj, ip6pubrange_created = \ + self._ensure_public_ipv6_range() + except Exception as e: + if initial_ipv6_cfg is not None: + try: + Configurations.update(self.apiclient, + "ipv6.offering.enabled", + initial_ipv6_cfg) + except Exception: + pass + if ip6prefix_created and ip6prefix_obj: + try: + del_cmd = (deleteGuestNetworkIpv6Prefix + .deleteGuestNetworkIpv6PrefixCmd()) + del_cmd.id = ip6prefix_obj.id + self.apiclient.deleteGuestNetworkIpv6Prefix(del_cmd) + except Exception: + pass + self.skipTest("Cannot set up public IPv6 range: %s" % e) + + svc = ISOLATED_NETWORK_SERVICES + + try: + # ---- Extension + NSP (no offering here; we create a dualstack one) ---- + _, ext_name = self._setup_extension_nsp_offering( + "extnet-ip6iso", supported_services=svc, for_vpc=True) + + # ---- Dualstack network offering ---- + _provider_map = {s.strip(): ext_name for s in svc.split(',')} + ds_offering_params = { + "name": "ExtNet-IP6-Offering-%s" % random_gen(), + "displaytext": "ExtNet IPv6 dualstack offering", + "guestiptype": "Isolated", + "traffictype": "GUEST", + "internetprotocol": "dualstack", + "supportedservices": svc, + "serviceProviderList": _provider_map, + "serviceCapabilityList": { + "SourceNat": {"SupportedSourceNatTypes": "peraccount"}, + }, + } + nw_offering = NetworkOffering.create(self.apiclient, ds_offering_params) + self.cleanup.append(nw_offering) + nw_offering.update(self.apiclient, state='Enabled') + self.logger.info("Dualstack offering '%s' enabled", nw_offering.name) + + # ---- Account + network + VM ---- + account, network, vm = self._create_account_network_vm( + nw_offering, name_suffix="ip6iso") + + # ========================================================== + # A. API: verify network and VM NIC get IPv6 attributes + # ========================================================== + self.logger.info("--- Sub-test A: API IPv6 attribute verification ---") + + nw_list = Network.list(self.apiclient, id=network.id, listall=True) + self.assertIsInstance(nw_list, list, + "Network.list returned no list") + self.assertGreater(len(nw_list), 0, "Network not found after creation") + nw = nw_list[0] + + self.assertIsNotNone(nw.ip6cidr, + "Network %s should have ip6cidr (dualstack offering)" % network.id) + self.assertIsNotNone(nw.ip6gateway, + "Network %s should have ip6gateway (dualstack offering)" % network.id) + ip6cidr = nw.ip6cidr + ip6gateway = nw.ip6gateway + self.logger.info("Network IPv6: gateway=%s cidr=%s", + ip6gateway, ip6cidr) + + nics = NIC.list(self.apiclient, + virtualmachineid=vm.id, + networkid=network.id) + self.assertIsInstance(nics, list, "NIC.list returned no list") + self.assertGreater(len(nics), 0, "VM NIC not found") + vm_ip6 = getattr(nics[0], 'ip6address', None) + self.assertIsNotNone(vm_ip6, + "VM NIC should have ip6address (SLAAC) after deploy") + self.logger.info("VM NIC IPv6: %s", vm_ip6) + self.logger.info("Sub-test A PASSED") + + # ========================================================== + # B. KVM namespace: IPv6 address on veth + radvd running + # ========================================================== + self.logger.info("--- Sub-test B: KVM namespace IPv6 verification ---") + + # The network namespace lives on the KVM host chosen by + # ensure-network-device, which is NOT necessarily the same host that + # runs the VM. Search every configured KVM host for the ip6-gateway + # state file written by config-dhcp-subnet. + self.assertTrue(self.kvm_host_configs, + "No KVM hosts configured — cannot verify namespace state") + + prefix_size = ip6cidr.split('/')[-1] + + kvm_ssh = None + kvm_host_ip = None + found_path = None + discover_cmd = ( + "for f in /var/lib/cloudstack/*/network-*/ip6-gateway; do " + "[ -f \"$f\" ] && grep -qF '%(gw)s' \"$f\" " + "&& echo \"$f\" && break; done" % {"gw": ip6gateway} + ) + for h in self.kvm_host_configs: + h_ip = h.get('ip', '') + h_user = h.get('username', 'root') + h_pass = h.get('password', '') + if not h_ip: + continue + try: + ssh = SshClient(h_ip, 22, h_user, h_pass) + out = ssh.execute(discover_cmd) + fp = next((l.strip() for l in out if l.strip()), None) + if fp: + kvm_ssh = ssh + kvm_host_ip = h_ip + found_path = fp + self.logger.info( + "Found ip6-gateway state on KVM host %s: %s", + h_ip, fp) + break + except Exception as e: + self.logger.warning( + "Could not search KVM host %s: %s", h_ip, e) + + self.assertIsNotNone( + found_path, + "No ip6-gateway state file containing %s found on any " + "configured KVM host (searched: %s)" + % (ip6gateway, + ", ".join(h.get('ip', '?') + for h in self.kvm_host_configs))) + + state_dir = found_path.rsplit('/ip6-gateway', 1)[0] + ns_id = state_dir.split('/')[-1].replace('network-', '') + ns_name = "cs-net-" + ns_id + radvd_conf_path = "%s/radvd/radvd.conf" % state_dir + radvd_pid_path = "%s/radvd/radvd.pid" % state_dir + self.logger.info("Discovered state_dir=%s namespace=%s", + state_dir, ns_name) + + # B1. Namespace exists + ns_out = kvm_ssh.execute( + "ip netns list 2>/dev/null | grep -c '^%s' || echo 0" % ns_name) + self.assertTrue( + any(line.strip() not in ('', '0') for line in ns_out), + "Namespace '%s' not found on KVM host %s" % (ns_name, kvm_host_ip)) + self.logger.info("B1 PASSED: namespace '%s' exists", ns_name) + + # B2. IPv6 gateway address is present on the guest veth + ip6_out = kvm_ssh.execute( + "ip netns exec '%s' ip -6 addr show 2>/dev/null" + " | grep '%s' || true" % (ns_name, ip6gateway)) + self.assertTrue( + any(ip6gateway in line for line in ip6_out), + "IPv6 gateway %s not found on any interface in namespace '%s'" + % (ip6gateway, ns_name)) + self.logger.info("B2 PASSED: IPv6 gateway %s present in namespace", + ip6gateway) + + # B3. radvd.conf exists and is non-empty + conf_check = kvm_ssh.execute( + "test -s '%s' && echo RADVD_CONF_OK" + " || echo RADVD_CONF_MISSING" % radvd_conf_path) + self.assertTrue( + any("RADVD_CONF_OK" in line for line in conf_check), + "radvd.conf not found or empty at %s" % radvd_conf_path) + self.logger.info("B3 PASSED: radvd.conf present at %s", + radvd_conf_path) + + # B4. radvd.conf has correct directives and prefix size + conf_lines = kvm_ssh.execute("cat '%s' 2>/dev/null" % radvd_conf_path) + conf_text = "\n".join(conf_lines) + self.assertIn("AdvSendAdvert on", conf_text, + "radvd.conf missing 'AdvSendAdvert on'") + self.assertIn("AdvAutonomous on", conf_text, + "radvd.conf missing 'AdvAutonomous on'") + self.assertIn("/" + prefix_size, conf_text, + "radvd.conf missing prefix size '/%s'" % prefix_size) + self.logger.info( + "B4 PASSED: radvd.conf directives correct " + "(AdvSendAdvert on, AdvAutonomous on, /%s)", prefix_size) + + # B5. radvd process is running inside the namespace + radvd_run = kvm_ssh.execute( + "pid=$(cat '%(pid)s' 2>/dev/null) && [ -n \"$pid\" ] && " + "ip netns exec '%(ns)s' ls /proc/$pid/status >/dev/null 2>&1 && " + "echo RADVD_RUNNING || echo RADVD_STOPPED" + % {"pid": radvd_pid_path, "ns": ns_name}) + self.assertTrue( + any("RADVD_RUNNING" in line for line in radvd_run), + "radvd not running inside namespace '%s' (pid file: %s)" + % (ns_name, radvd_pid_path)) + self.logger.info("B5 PASSED: radvd is running inside namespace '%s'", + ns_name) + + self.logger.info( + "Sub-test B PASSED: ns=%s gw=%s radvd=running", + ns_name, ip6gateway) + + finally: + # Restore ipv6.offering.enabled + if initial_ipv6_cfg is not None: + try: + Configurations.update(self.apiclient, + "ipv6.offering.enabled", + initial_ipv6_cfg) + self.logger.info("Restored ipv6.offering.enabled to %s", + initial_ipv6_cfg) + except Exception as e: + self.logger.warning( + "Could not restore ipv6.offering.enabled: %s", e) + + # Remove public IPv6 range if this test created it + if ip6pubrange_created and ip6pubrange_obj: + try: + ip6pubrange_obj.delete(self.apiclient) + self.logger.info("Deleted public IPv6 range %s", + getattr(ip6pubrange_obj, 'ip6cidr', '')) + except Exception as e: + self.logger.warning( + "Could not delete public IPv6 range: %s", e) + + # Remove IPv6 prefix if this test created it + if ip6prefix_created and ip6prefix_obj: + try: + del_cmd = (deleteGuestNetworkIpv6Prefix + .deleteGuestNetworkIpv6PrefixCmd()) + del_cmd.id = ip6prefix_obj.id + self.apiclient.deleteGuestNetworkIpv6Prefix(del_cmd) + self.logger.info("Deleted guest IPv6 prefix %s", + ip6prefix_obj.prefix) + except Exception as e: + self.logger.warning( + "Could not delete guest IPv6 prefix: %s", e) + + # ---- Explicit ordered cleanup ---- + try: + vm.delete(self.apiclient, expunge=True) + self.cleanup = [o for o in self.cleanup if o != vm] + except Exception: + pass + try: + network.delete(self.apiclient) + self.cleanup = [o for o in self.cleanup if o != network] + except Exception: + pass + try: + self._teardown_extension() + except Exception: + pass + + self.logger.info("test_11 PASSED") diff --git a/test/integration/smoke/test_network_traffic_type_api.py b/test/integration/smoke/test_network_traffic_type_api.py new file mode 100644 index 000000000000..10e9df470988 --- /dev/null +++ b/test/integration/smoke/test_network_traffic_type_api.py @@ -0,0 +1,73 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import ( + TrafficType, + PhysicalNetwork, + Zone, +) +from nose.plugins.attrib import attr + + +class TestTrafficTypeApi(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.test_client = super(TestTrafficTypeApi, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls._cleanup = [] + cls.services["advanced_sg"]["zone"]["name"] = "TestTrafficTypeApi-zone" + cls.zone = Zone.create(cls.api_client, cls.services["advanced_sg"]["zone"]) + cls._cleanup.append(cls.zone) + + cls.physical_network = PhysicalNetwork.create( + cls.api_client, + cls.services["l2-network"], + isolationmethods="VLAN", + zoneid=cls.zone.id, + ) + cls._cleanup.append(cls.physical_network) + + @classmethod + def tearDownClass(cls): + super(TestTrafficTypeApi, cls).tearDownClass() + + + @attr(tags=["advanced"], required_hardware="false") + def test_list_api_fields(self): + traffic_type = TrafficType.add( + self.api_client, + physicalnetworkid=self.physical_network.id, + kvmnetworklabel="kvm", + traffictype="Public", + vlan="100", + ).traffictype + + traffic_types = TrafficType.list( + self.api_client, + physicalnetworkid=self.physical_network.id + ) + + assert len(traffic_types) == 1 + response = traffic_types[0] + self.assertEqual(response.id, traffic_type.id) + self.assertEqual(response.kvmnetworklabel, "kvm") + self.assertEqual(response.traffictype, "Public") + self.assertEqual(response.vlan, "100") + self.assertEqual(response.isolationmethods, "VLAN") diff --git a/test/integration/smoke/test_public_ip_range.py b/test/integration/smoke/test_public_ip_range.py index 997716caaaf4..fb9fe2494d57 100644 --- a/test/integration/smoke/test_public_ip_range.py +++ b/test/integration/smoke/test_public_ip_range.py @@ -145,10 +145,10 @@ def test_dedicate_public_ip_range_for_system_vms(self): # 7. Delete the Public IP range services = { - "gateway":"192.168.99.1", + "gateway":"10.1.99.1", "netmask":"255.255.255.0", - "startip":"192.168.99.2", - "endip":"192.168.99.200", + "startip":"10.1.99.2", + "endip":"10.1.99.200", "forvirtualnetwork":self.services["forvirtualnetwork"], "zoneid":self.services["zoneid"], "vlan":self.services["vlan"] @@ -344,10 +344,10 @@ def test_dedicate_public_ip_range_for_system_vms_01_ssvm(self): self.skipTest("An existing IP range defined for system vms, aborting test") services = { - "gateway":"192.168.100.1", + "gateway":"10.1.100.1", "netmask":"255.255.255.0", - "startip":"192.168.100.2", - "endip":"192.168.100.200", + "startip":"10.1.100.2", + "endip":"10.1.100.200", "forvirtualnetwork":self.services["forvirtualnetwork"], "zoneid":self.services["zoneid"], "vlan":self.services["vlan"] @@ -372,10 +372,10 @@ def test_dedicate_public_ip_range_for_system_vms_02_cpvm(self): self.skipTest("An existing IP range defined for system vms, aborting test") services = { - "gateway":"192.168.200.1", + "gateway":"10.1.200.1", "netmask":"255.255.255.0", - "startip":"192.168.200.2", - "endip":"192.168.200.200", + "startip":"10.1.200.2", + "endip":"10.1.200.200", "forvirtualnetwork":self.services["forvirtualnetwork"], "zoneid":self.services["zoneid"], "vlan":self.services["vlan"] diff --git a/test/integration/smoke/test_secondary_storage.py b/test/integration/smoke/test_secondary_storage.py index 4b26950ea646..2ed60a844256 100644 --- a/test/integration/smoke/test_secondary_storage.py +++ b/test/integration/smoke/test_secondary_storage.py @@ -136,7 +136,11 @@ def test_01_sys_vm_start(self): 'Up', "Check state of primary storage pools is Up or not" ) - for _ in range(2): + # Poll until all SSVMs are Running, or timeout after 3 minutes + timeout = 180 + interval = 15 + list_ssvm_response = [] + while timeout > 0: list_ssvm_response = list_ssvms( self.apiclient, systemvmtype='secondarystoragevm', @@ -154,10 +158,12 @@ def test_01_sys_vm_start(self): "Check list System VMs response" ) - for ssvm in list_ssvm_response: - if ssvm.state != 'Running': - time.sleep(30) - continue + if all(ssvm.state == 'Running' for ssvm in list_ssvm_response): + break + + time.sleep(interval) + timeout -= interval + for ssvm in list_ssvm_response: self.assertEqual( ssvm.state, diff --git a/test/integration/smoke/test_service_offerings.py b/test/integration/smoke/test_service_offerings.py index c6a14a64471d..a523cd0e6987 100644 --- a/test/integration/smoke/test_service_offerings.py +++ b/test/integration/smoke/test_service_offerings.py @@ -35,7 +35,8 @@ get_domain, get_zone, get_test_template, - list_hosts) + list_hosts, + is_config_suitable) from nose.plugins.attrib import attr import time @@ -356,6 +357,164 @@ def test_05_create_service_offering_with_root_encryption_type(self): ) return + @attr( + tags=[ + "advanced", + "smoke", + "basic"], + required_hardware="false") + def test_06_create_service_offering_lease_enabled(self): + """ + 1. Enable lease feature + 2. Create a service_offering + 3. Verify service offering lease properties + """ + self.update_lease_feature("true") + + service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["tiny"], + name="tiny-lease-svc-offering", + leaseduration=10, + leaseexpiryaction="STOP" + ) + self.cleanup.append(service_offering) + + self.debug( + "Created service offering with ID: %s" % + service_offering.id) + + list_service_response = list_service_offering( + self.apiclient, + id=service_offering.id + ) + + self.assertNotEqual( + len(list_service_response), + 0, + "Check Service offering is created" + ) + + self.assertEqual( + list_service_response[0].leaseduration, + 10, + "Confirm leaseduration" + ) + + self.assertEqual( + list_service_response[0].leaseexpiryaction, + "STOP", + "Confirm leaseexpiryaction" + ) + return + + @attr( + tags=[ + "advanced", + "smoke", + "basic"], + required_hardware="false") + def test_07_create_service_offering_without_lease_disabled_feature(self): + """ + 1. Disable lease feature + 2. Create a service_offering with lease option + 3. Verify service offering for NO lease properties + """ + self.update_lease_feature("true") + service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["tiny"], + name="tiny-svc-offering-novalue-lease" + ) + self.cleanup.append(service_offering) + + self.debug( + "Created service offering with ID: %s" % + service_offering.id) + + list_service_response = list_service_offering( + self.apiclient, + id=service_offering.id + ) + + self.assertNotEqual( + len(list_service_response), + 0, + "Check Service offering is created" + ) + + self.assertIsNone( + list_service_response[0].leaseduration, + "Confirm No leaseduration" + ) + self.assertIsNone( + list_service_response[0].leaseexiryaction, + "Confirm leaseexpiryaction is not set" + ) + return + + @attr( + tags=[ + "advanced", + "smoke", + "basic"], + required_hardware="false") + def test_08_create_service_offering_lease_disabled(self): + """ + 1. Disable lease feature + 2. Create a service_offering with lease option + 3. Verify service offering for NO lease properties + """ + self.update_lease_feature("false") + service_offering = ServiceOffering.create( + self.apiclient, + self.services["service_offerings"]["tiny"], + name="tiny-lease-svc-offering-disabled", + leaseduration=10, + leaseexpiryaction="STOP" + ) + self.cleanup.append(service_offering) + + self.debug( + "Created service offering with ID: %s" % + service_offering.id) + + list_service_response = list_service_offering( + self.apiclient, + id=service_offering.id + ) + + self.assertNotEqual( + len(list_service_response), + 0, + "Check Service offering is created" + ) + + self.assertIsNone( + list_service_response[0].leaseduration, + "Confirm No leaseduration" + ) + self.assertIsNone( + list_service_response[0].leaseexiryaction, + "Confirm leaseexpiryaction is not set" + ) + return + + def update_lease_feature(self, value=None): + # Update global setting for "instance.lease.enabled" + Configurations.update(self.apiclient, + name="instance.lease.enabled", + value=value + ) + + # Verify that the above mentioned settings are set to true + if not is_config_suitable( + apiclient=self.apiclient, + name='instance.lease.enabled', + value=value): + self.fail(f'instance.lease.enabled should be: {value}') + + class TestServiceOfferings(cloudstackTestCase): diff --git a/test/integration/smoke/test_ssl_offloading.py b/test/integration/smoke/test_ssl_offloading.py new file mode 100644 index 000000000000..5f0ea9adc546 --- /dev/null +++ b/test/integration/smoke/test_ssl_offloading.py @@ -0,0 +1,568 @@ +# 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. + +from marvin.codes import FAILED +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import wait_until +from marvin.lib.base import (Account, + Project, + UserData, + SslCertificate, + Template, + NetworkOffering, + ServiceOffering, + VirtualMachine, + Network, + VPC, + VpcOffering, + PublicIPAddress, + LoadBalancerRule) +from marvin.lib.common import (get_domain, get_zone, get_test_template) +from nose.plugins.attrib import attr + +import os +import subprocess +import logging + + +_multiprocess_shared_ = True + +DOMAIN = "test-ssl-offloading.cloudstack.org" +CONTENT = "Test page" +FULL_CHAIN = "/tmp/full_chain.crt" + +CERT = { + "privatekey": """-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCph7jsoMCQirRn +3obuvgnnefTXRQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0 +rXjjgsVT3r4bv+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRx +kB0klwUcj/jk/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0q +t6KDMkUwv8fyzrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaM +Pe7eqSFzxunF9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXq +HNUwYkALAgMBAAECggEAK5JiiQ7X7053B6s96uaVDRVfRGTNKa5iMXBNDHq3wbHZ +X4IJAVr+PE7ivxdKco3r45fT11X9ZpUsssdTJsZZiTDak69BTiFcaaRCnmqOIlpd +J7vb6TMrTIW8RvxQ0M/txm6DuNHLibqJX5a2pszZ13l5cwECfF9/v/XLJTTukCbu +6D/f3fBVFl1tM8y9saOEYLkdb4dILWY61bVSDNswgprz2EV1SFnk5jxz2FuBrM/Q ++7hINvjDcaRvcm59hRb1rkljv7S10VoNw/CFkU451csJkUe4vWZwB8lZK/XxLQG0 +HEdS1zU1XY8H8Y1RCrxjGRyiiWsBtUThhWYlPrGCoQKBgQDkP09YAlKqXhT69Kx5 +keg2i1jV2hA73zWbWXt9xp5jG5r3pl3m170DvKL93YIDnHtpTC56mlzGrzS7DSTN +p0buY9Qb3fkJxunCpPVFo0HMFkpeR77ax0v34NzSohlRLKFo5R2M1cmDfbVbnSSl +MB57FfRRMxzjrk+dJvjOeJsxjwKBgQC+JLb4B8CZjpurXYg3ySiRqFsCqkqob+kf +9dR+rWvcR6vMTEyha0hUlDvTikDepU2smYR4oPHfdcXF9lAJ7T02UmQDeizAqR68 +u9e+yS0q3tdRnPPZmXJfaDCXG1hKMqF4YA5Vs0XAjleF3zHB+vBLrnlPpShtd/Mu +sWTpxICTxQKBgQDSr/n+pE5IQwYczOO0aFGwn5pF9L9NdPHXz5aleETV+TJn7WL6 +ZiRsoaDWs7SCvtxQS2kP9RM0t5/2FeDmEMXx4aZ2fsSWGM3IxVo+iL+Aswa81n8/ +Ff5y9lb/+29hNdBcsjk/ukwEG3Lf+UNNVAie15oppgPByzJkPwgmFsAy0wKBgHDX +/TZp82WuerhSw/rHiSoYjhqg0bnw4Ju1Gy0q4q5SYqTWS0wpDT4U0wSSMjlwRQ6/ +9RxZ9/G0RXFc4tdhUkig0PY3VcPpGnLL0BhL8GBW69ZlnVpwdK4meV/UPKucLLPx +3dACmszSLSMn+LG0qVNg8mHQFJQS8eGuKcOKePw5AoGACuxtefROKdKOALh4lTi2 +VOwPZ+1jxsm6lKNccIEvbUpe3UXPgNWpJiDX8mUcob4/NBLzmV3BUVKbG7Exbo5J +LoMfp7OsztWUFwt7YAvRfS8fHdhkEsxEf3T72ADieH5ZAuXFF+K0H3r6HtWPD4ws +mTJjGP4+Bl/dFakA5FJcjHg= +-----END PRIVATE KEY-----""", + "certificate": """-----BEGIN CERTIFICATE----- +MIIFKjCCAxKgAwIBAgIUJ7BtN56KI8OuzbbM8SdtCLCB2UgwDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwHhcNMjUwNjIzMTMxMzA3WhcNMzUwNjIxMTMxMzA3WjBoMQswCQYDVQQGEwJY +WDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMQ8wDQYDVQQKDAZBcGFjaGUxEzAR +BgNVBAsMCkNsb3VkU3RhY2sxGTAXBgNVBAMMECouY2xvdWRzdGFjay5vcmcwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCph7jsoMCQirRn3obuvgnnefTX +RQYd9tF9k2aCVkTiiisvC39px7MGdgvDXADhD9fmR7oyXVQlfNu0rXjjgsVT3r4b +v+DVi81YGXnuU7h10yCOZJt21i6QGHN1CS0/TAfg0UhlACCEYNRxkB0klwUcj/jk +/AKil1DoUGpvAm2gZsek/njb76/AeIfxc+Es4ZOPCVqQOHp6gI0qt6KDMkUwv8fy +zrpScygMUPVYrLmm6D0pn8yd3ihW07wGxMjND6UgOnao8t6H3LaMPe7eqSFzxunF +9NFFjnUrKcHZZSledDM/37Kbqb/8T5f+4SwjioS1OdPCh8ApdiXqHNUwYkALAgMB +AAGjgdUwgdIwKwYDVR0RBCQwIoIQKi5jbG91ZHN0YWNrLm9yZ4IOY2xvdWRzdGFj +ay5vcmcwHQYDVR0OBBYEFCcq7jrdsqTD+Xi85DCqjYdL1gOqMIGDBgNVHSMEfDB6 +oWKkYDBeMQswCQYDVQQGEwJYWDELMAkGA1UECAwCWFgxCzAJBgNVBAcMAlhYMRMw +EQYDVQQKDApDbG91ZFN0YWNrMQ8wDQYDVQQLDAZBcGFjaGUxDzANBgNVBAMMBkFw +YWNoZYIURVB9+qvRJyOnJnqmYOw467vW3vQwDQYJKoZIhvcNAQELBQADggIBACld +lEXgn/A4/kZQbLwwMxBvaoPDDaDaYVpPbOoPw7a8YkrL0rmPIc04PyX9GAqxdC+c +qaEXvmp3I+BdT13XGcBosXO8uEQ3kses9F3MhOHORPS2mJag7t4eLnNX/0CgKTlR +6yC2Gu7d3xPNJ+CKMxekdoF31StEFNAYI/La/q3D+IGsRCbrVu3xpPaw2XlXI7Ro +RU7yebVmQPSNc75bm8Ydo1cdYtz9h8PVnc+6ThhSrdS3jYScj9DrX5ZJaKuZqSlu +0ZqFXoBflme+cYB7nb9HqnIO67r9vzd2dTcErJVAk5jQqG5Y38d1tingDx1A5opU +z4BkXEbHNV6VXYUQ5VE0dXO2sNvXVJrstwMPE8d3EvbX/1gWj8kuymbskrCjySE4 +4Yztkb0dsJkVU793lz3EV75DsXvj3gevK049nPv2Grt1+rTgFNa6NJnLvKIKk/mv +fWjxbK2b/AAJ1ci6xtw/vKmIWoEu6uEMIJmhfBwuP+VnVJWJbmYXpNW/L5g21B76 +Fn8RuQa3mlm5lZrxEcJ/b6fF+2NPJwj7sh6l688VtNXoVSSyXUeV5HwqCv+YMjKn +CtwpEN/eNHMbrkJvgYwSoOzqhV/wpmNi28S7MOm66JMECHOXOhk/eX2chIEjiVna +MXhvr/Twfj2N4gNVtcgXkrk39HEYjk5+uF7SdNf4 +-----END CERTIFICATE-----""", + "certchain": """-----BEGIN CERTIFICATE----- +MIIFQzCCAysCFEVQffqr0ScjpyZ6pmDsOOu71t70MA0GCSqGSIb3DQEBCwUAMF4x +CzAJBgNVBAYTAlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoM +CkNsb3VkU3RhY2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMB4X +DTI1MDYxNjEwMjc1NloXDTMwMDYxNTEwMjc1NlowXjELMAkGA1UEBhMCWFgxCzAJ +BgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEGA1UECgwKQ2xvdWRTdGFjazEPMA0G +A1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFjaGUwggIiMA0GCSqGSIb3DQEBAQUA +A4ICDwAwggIKAoICAQCLiQmSjrht15R1F+r79m/LZN5hsfQBGp+dy+yrtsWfOOur +RdXAwgbLxxsyKMQKWCQxlRI7wdhqh0L0ZBrIr9MjltYqsqLAoLmgY4eG/f6G8YGr +O/rxzfwTLbCeaIseF/OMA6Sz125HXYp1bltYK4LsuC7tihZXbeVa5pUGs3Jwgcfx +LYm4eB42Hp7Eg05uL8LbwT/1AjcwoWkTewKAWXA83zgLRDFDbl1t0IPHI4cdVvia +BNwNbG49ZCF6OgmokSarQSe4Vbems1u9T9pAySXAVjEYBqFjKWyswpdr782uNLmB +lCGm0pDeJ9/WASxbTJr7k9H6ZpnaHr54DG6ZqennWMz8w6r2pf7bp/EGZ3mZQ4s3 +5ylSP4cQt8CSSI8k2CflPGUyytUAiWlDS3qSyIuAOPKXDg7wIpcbwcu4VMeKnH0Z +x7Uu9j1UDZEZoSu6UI/VInTl47k1/ECD+AO9yBzZSv+pTQmO3/Im3CcxsTHmVd5s +Tl0CJ/jWNpo9DAMtmGvt6CBWBXGRsO2XNk7djRcq2CubiCpvODg+7CcR6CiZK73L +1aOisLiq3+ofiJSSXRRuKtJlkQ4eSPSbYWkNJcKmIhbCoYOdH/Pe3/+RHjvNc1kO +OUb+icmfzcMVAs3C5jybpazsfjDNQZXWAFx4FLDcqOVbrCwom+tMukw+hzlZnwID +AQABMA0GCSqGSIb3DQEBCwUAA4ICAQAdexoMwn+Ol1A3+NOHk9WJZX+t2Q8/9wWb +K+jSVleSfXXWsB1mC3fABVJQdCxtXCH3rpt4w7FK6aUes9VjqAHap4tt9WBq0Wqq +vvMURFHfllxEM31Z35kBOCSQY9xwpGV1rRh/zYs4h55YixomR3nXNZ9cI8xzlSCi +sMG0mv0y+yxPohKrZj3AzLYz/M11SimSoyRPIANI+cUg1nJXyQoHzVHWEp1Nj0HB +M/GW05cxsWea7f5YcAW1JQI3FOkpwb72fIZOtMDa4PO8IYWXJAeAc/chw745/MTi +Rvl2NT4RZBAcrSNbhCOzRPG/ZiG+ArQuCluZ9HHAXRBMTtlLk5DO4+XxZlyGpjwf +uKniK8dccy9uU0ho73p9SNDhXH0yb9Naj8vd9NWzCUYaaBXt/92cIyhaAHAVFxJu +o6jr2FLbnhSGF9EO/tHvF7LxZv1dnbInvlWHwoFQjwmoeB+e17lHBdPMnWnPKBZe +jA2VH/IzGCucWuWQhruummO5GT8Z6F4jBwvafBo+QARKPZgEBpx3LycXrpkYI3LT +GGOpGCxFt5tVZOEsC/jQ5rIljNSeTzWmzfNRn/yRUW97uWsrzcQIBAUtu/pQnyFQ +WCnC1ipCp1zhJsXAFUKuqEfLngXodOvC4tAOr76h11S57o5lN4506Poq2mWgAZe/ +JZr9MEn1+w== +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIFnzCCA4egAwIBAgIUcUNMqgWoDLsvMj0YmEudj60EG5swDQYJKoZIhvcNAQEL +BQAwXjELMAkGA1UEBhMCWFgxCzAJBgNVBAgMAlhYMQswCQYDVQQHDAJYWDETMBEG +A1UECgwKQ2xvdWRTdGFjazEPMA0GA1UECwwGQXBhY2hlMQ8wDQYDVQQDDAZBcGFj +aGUwIBcNMjUwNjE2MTAyNzM2WhgPMjEyNTA1MjMxMDI3MzZaMF4xCzAJBgNVBAYT +AlhYMQswCQYDVQQIDAJYWDELMAkGA1UEBwwCWFgxEzARBgNVBAoMCkNsb3VkU3Rh +Y2sxDzANBgNVBAsMBkFwYWNoZTEPMA0GA1UEAwwGQXBhY2hlMIICIjANBgkqhkiG +9w0BAQEFAAOCAg8AMIICCgKCAgEAwVQaePulUM523gKw168ToYp+gt05bXbu4Gg8 +uaRDKhnRAX1sEgYwkQ36Q+iTDEM9sKRma8lMNMIqkZMQdk6sIGX6BL+6wUOb7mL0 +5+I0yO9i8ooaGgNaeNvZftNIRlLsnPMGJaeom2/66XV4CsMqoZKaJ1H/I8N+bAeD +GvrBx+B4l9D3G390nQvot9JUzrJgGuLl0KDHapvhlR39cCgEfIii02uX1iy0qXlV +b+G1kLvpeC7T+lsJxondPJ69aO3lbDv/izyWw7qqBC57UhT/oKDxJmjQqklqzhgt +nM/p3YE7M0nkRi3LnRmsZBz7o1DRf+M29zypKzXVk1aJflL46AtLMmpDIzVrEB2M +q7o47rstXusYRYsBCqGTgdI1fV/CkDsZY5XkPZh2dsjZCHIS4P03OqFGsc6PQha2 ++y2AhV1pvywkDl48kPKSukHfV1RtaPZUZtcQKztwHH+aFfo9mD8z0H2HcExdXKzd +jhRhI9ZSwFj3HEN9f5P8fS3lf5+fV7EEbG4NisieBj/UivW6QiTHpLD7wRLIUt2g +XgXNF0lfJzYHbIcxQ6kfC5McU2fu6mUC+p/pNN8G0POS3S2T55tEUqLL4N0SadQy +N1TZlTd2xTn+Hb6WlG0f5m97xGcNlGHKBvntFrHvOIfkEQ9ne3MlOO1Gjlintowo +fRGf15kCAwEAAaNTMFEwHQYDVR0OBBYEFM4WEQJpN9M07Q8CHq+5owG93Dj8MB8G +A1UdIwQYMBaAFM4WEQJpN9M07Q8CHq+5owG93Dj8MA8GA1UdEwEB/wQFMAMBAf8w +DQYJKoZIhvcNAQELBQADggIBABr5RKGc/rKx4fOgyXNJR4aCJCTtPZKs4AUCCBYz +cWOjJYGNvThOPSVx51nAD8+YP2BwkOIPfhl2u/+vQSSbz4OlauXLki2DUN8E2OFe +gJfxPDzWOfAo/QOJcyHwSlnIQjiZzG2lK3eyf5IFnfQILQzDvXaUYvMBkl2hb5Q7 +44H6tRw78uuf/KsT4rY0bBFMN5DayjiyvoIDUvzCRqcb2KOi9DnZ7pXjduL7tO0j +PhlQ24B77LVUUAvydIGUzmbhGC2VvY1qE7uaYgYtgSUZ0zSjJrHjUjVLMzRouNP7 +jpbBQRAcP4FDcOFZBHogunA0hxQdm0d8u3LqDYPNS0rpfW0ddU/72nfBX4bnoDEN ++anw4wOgFuUcoEThALWZ9ESVKxXQ9Fpvd6FRW8fLLqhXAuli1BqP1c1WRxagldYe +nPGm/FGZyJ2xOak9Uigi9NAQ/vX6CEfgcJgFZmCo8EKH0d4Ut72vGUcPqiUhT2EI +AFAd6drSyoUdXXniSMWky9Vrt+qtLuAD1nhHTv8ZPdItXokoiD6ea/4xrbUZn0qY +lLMDyfY76UVF0ruTR2Q6IdSq/zSggdwgkTooOW4XZcRf5l/ZnoeVQ1QH9C85SIKH +IKZwPeGUm+EntmpuCBDmQSHLRCGEThd64iOAjqLR6arLj4TBJzBrZsGHFJbm0OcI +dwa9 +-----END CERTIFICATE-----""", + "enabledrevocationcheck": False +} + +# Install apache2 via userdata +USER_DATA="""I2Nsb3VkLWNvbmZpZwpydW5jbWQ6CiAgLSBzdWRvIGFwdC1nZXQgdXBkYXRlCiAgLSBzdWRvIGFw +dC1nZXQgaW5zdGFsbCAteSBhcGFjaGUyCiAgLSBzdWRvIHN5c3RlbWN0bCBlbmFibGUgYXBhY2hl +MgogIC0gc3VkbyBzeXN0ZW1jdGwgc3RhcnQgYXBhY2hlMgogIC0gZWNobyAiVGVzdCBwYWdlIiB8 +c3VkbyB0ZWUgL3Zhci93d3cvaHRtbC90ZXN0Lmh0bWwK""" +# #cloud-config +# runcmd: +# - sudo apt-get update +# - sudo apt-get install -y apache2 +# - sudo systemctl enable apache2 +# - sudo systemctl start apache2 +# - echo "Test page" |sudo tee /var/www/html/test.html + +class TestSslOffloading(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + + testClient = super(TestSslOffloading, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.hypervisor = testClient.getHypervisorInfo() + + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + + # Save full chain as a file + with open(FULL_CHAIN, "w", encoding="utf-8") as f: + f.write(CERT["certchain"]) + + # Register template if needed + if cls.hypervisor.lower() == 'simulator': + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + else: + cls.template = Template.register( + cls.apiclient, + cls.services["test_templates_cloud_init"][cls.hypervisor.lower()], + zoneid=cls.zone.id, + hypervisor=cls.hypervisor, + ) + cls.template.download(cls.apiclient) + cls._cleanup.append(cls.template) + + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + # Create service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["big"] # 512MB memory + ) + cls._cleanup.append(cls.service_offering) + + # Create network offering + cls.services["isolated_network_offering"]["egress_policy"] = "true" + cls.network_offering = NetworkOffering.create(cls.apiclient, + cls.services["isolated_network_offering"], + conservemode=True) + cls.network_offering.update(cls.apiclient, state='Enabled') + + cls._cleanup.append(cls.network_offering) + + #Create an account, network, VM and IP addresses + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.user = cls.account.user[0] + cls.userapiclient = cls.testClient.getUserApiClient(cls.user.username, cls.domain.name) + + cls.logger = logging.getLogger("TestSslOffloading") + cls.stream_handler = logging.StreamHandler() + cls.logger.setLevel(logging.DEBUG) + cls.logger.addHandler(cls.stream_handler) + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + + def tearDown(self): + super(TestSslOffloading, self).tearDown() + + @classmethod + def tearDownClass(cls): + super(TestSslOffloading, cls).tearDownClass() + # Remove full chain file + if os.path.exists(FULL_CHAIN): + os.remove(FULL_CHAIN) + + def wait_for_service_ready(self, command, expected, retries=60): + output = None + self.logger.debug("======================================") + self.logger.debug("Checking output of command '%s', expected result: '%s'" % (command, expected)) + def check_output(): + try: + output = subprocess.check_output(command + ' 2>&1', shell=True).strip().decode('utf-8') + except Exception as e: + self.logger.debug("Failed to get output of command '%s': '%s'" % (command, e)) + if expected is None: + self.logger.debug("But it is expected") + return True, None + return False, None + self.logger.debug("Output of command '%s' is '%s'" % (command, output)) + if expected is None: + self.logger.debug("But it is expected to be None") + return False, None + return (expected in output), None + + res, _ = wait_until(10, retries, check_output) + if not res: + self.fail("Failed to wait for http server to show content '%s'. The output is '%s'" % (expected, output)) + + @attr(tags = ["advanced", "advancedns", "smoke"], required_hardware="true") + def test_01_ssl_offloading_isolated_network(self): + """Test to create Load balancing rule with SSL offloading""" + + # Validate: + # 1. Create isolated network and vm instance + # 2. create LB with port 80 -> 80, verify the website (should get expected content) + # 3. create LB with port 443 -> 80, verify the website (should not work) + # 4. add cert to LB with port 443 + # 5. verify the website (should get expected content) + # 6. remove cert from LB with port 443 + # 7. delete SSL certificate + + # Register Userdata + self.userdata = UserData.register(self.apiclient, + name="test-userdata", + userdata=USER_DATA, + account=self.account.name, + domainid=self.account.domainid + ) + + # Upload SSL Certificate + self.sslcert = SslCertificate.create(self.apiclient, + CERT, + name="test-ssl-certificate", + account=self.account.name, + domainid=self.account.domainid) + + # 1. Create network + self.network = Network.create(self.apiclient, + zoneid=self.zone.id, + services=self.services["network"], + domainid=self.domain.id, + account=self.account.name, + networkofferingid=self.network_offering.id) + self.cleanup.append(self.network) + + self.services["virtual_machine"]["networkids"] = [str(self.network.id)] + + # Create vm instance + self.vm_1 = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.name, + domainid=self.account.domainid, + userdataid=self.userdata.userdata.id, + serviceofferingid=self.service_offering.id + ) + self.cleanup.append(self.vm_1) + + self.public_ip = PublicIPAddress.create( + self.apiclient, + self.account.name, + self.zone.id, + self.account.domainid, + self.services["virtual_machine"], + self.network.id) + + # 2. create LB with port 80 -> 80, verify the website (should get expected content). + # firewall is open by default + lb_http = { + "name": "http", + "alg": "roundrobin", + "privateport": 80, + "publicport": 80, + "protocol": "tcp" + } + lb_rule_http = LoadBalancerRule.create( + self.apiclient, + lb_http, + self.public_ip.ipaddress.id, + accountid=self.account.name, + domainid=self.domain.id, + networkid=self.network.id + ) + lb_rule_http.assign(self.apiclient, [self.vm_1]) + command = "curl -sL --connect-timeout 3 http://%s/test.html" % self.public_ip.ipaddress.ipaddress + # wait 10 minutes until the webpage is available. it returns "503 Service Unavailable" if not available + self.wait_for_service_ready(command, CONTENT, 60) + + # 3. create LB with port 443 -> 80, verify the website (should not work) + # firewall is open by default + lb_https = { + "name": "https", + "alg": "roundrobin", + "privateport": 80, + "publicport": 443, + "protocol": "ssl" + } + lb_rule_https = LoadBalancerRule.create( + self.apiclient, + lb_https, + self.public_ip.ipaddress.id, + accountid=self.account.name, + domainid=self.domain.id, + networkid=self.network.id + ) + lb_rule_https.assign(self.apiclient, [self.vm_1]) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + # 4. add cert to LB with port 443 + lb_rule_https.assignCert(self.apiclient, self.sslcert.id) + + # 5. verify the website (should get expected content) + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -sL --connect-timeout 3 -k --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + command = "curl -sL --connect-timeout 3 --cacert %s --resolve %s:443:%s https://%s/test.html" % (FULL_CHAIN, DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + # 6. remove cert from LB with port 443 + lb_rule_https.removeCert(self.apiclient) + + # 7. delete SSL certificate + self.sslcert.delete(self.apiclient) + + @attr(tags = ["advanced", "advancedns", "smoke"], required_hardware="true") + def test_02_ssl_offloading_project_vpc(self): + """Test to create Load balancing rule with SSL offloading in VPC in user project""" + + # Validate: + # 1. Create VPC, VPC tier and vm instance + # 2. create LB with port 80 -> 80, verify the website (should get expected content) + # 3. create LB with port 443 -> 80, verify the website (should not work) + # 4. add cert to LB with port 443 + # 5. verify the website (should get expected content) + # 6. remove cert from LB with port 443 + # 7. delete SSL certificate + + # Create project by user + self.project = Project.create( + self.userapiclient, + self.services["project"] + ) + self.cleanup.append(self.project) + + # Register Userdata by user + self.userdata = UserData.register(self.userapiclient, + name="test-user-userdata", + userdata=USER_DATA, + projectid=self.project.id + ) + + # Upload SSL Certificate by user + self.sslcert = SslCertificate.create(self.userapiclient, + CERT, + name="test-user-ssl-certificate", + projectid=self.project.id + ) + + # 1. Create VPC and VPC tier + vpcOffering = VpcOffering.list(self.userapiclient, name="Default VPC offering") + self.assertTrue(vpcOffering is not None and len( + vpcOffering) > 0, "No VPC offerings found") + + self.vpc = VPC.create( + apiclient=self.userapiclient, + services=self.services["vpc_vpn"]["vpc"], + vpcofferingid=vpcOffering[0].id, + zoneid=self.zone.id, + projectid=self.project.id + ) + self.cleanup.append(self.vpc) + + networkOffering = NetworkOffering.list( + self.userapiclient, name="DefaultIsolatedNetworkOfferingForVpcNetworks") + self.assertTrue(networkOffering is not None and len( + networkOffering) > 0, "No VPC based network offering") + + self.network = Network.create( + apiclient=self.userapiclient, + services=self.services["vpc_vpn"]["network_1"], + networkofferingid=networkOffering[0].id, + zoneid=self.zone.id, + vpcid=self.vpc.id, + projectid=self.project.id + ) + self.cleanup.append(self.network) + + self.services["virtual_machine"]["networkids"] = [str(self.network.id)] + + # Create vm instance + self.vm_2 = VirtualMachine.create( + self.userapiclient, + self.services["virtual_machine"], + templateid=self.template.id, + userdataid=self.userdata.userdata.id, + serviceofferingid=self.service_offering.id, + projectid=self.project.id + ) + self.cleanup.append(self.vm_2) + + self.public_ip = PublicIPAddress.create( + self.userapiclient, + zoneid=self.zone.id, + services=self.services["virtual_machine"], + networkid=self.network.id, + vpcid=self.vpc.id, + projectid=self.project.id + ) + + # 2. create LB with port 80 -> 80, verify the website (should get expected content). + # firewall is open by default + lb_http = { + "name": "http", + "alg": "roundrobin", + "privateport": 80, + "publicport": 80, + "protocol": "tcp" + } + lb_rule_http = LoadBalancerRule.create( + self.userapiclient, + lb_http, + self.public_ip.ipaddress.id, + networkid=self.network.id, + projectid=self.project.id + ) + lb_rule_http.assign(self.userapiclient, [self.vm_2]) + command = "curl -sL --connect-timeout 3 http://%s/test.html" % self.public_ip.ipaddress.ipaddress + # wait 10 minutes until the webpage is available. it returns "503 Service Unavailable" if not available + self.wait_for_service_ready(command, CONTENT, 60) + + # 3. create LB with port 443 -> 80, verify the website (should not work) + # firewall is open by default + lb_https = { + "name": "https", + "alg": "roundrobin", + "privateport": 80, + "publicport": 443, + "protocol": "ssl" + } + lb_rule_https = LoadBalancerRule.create( + self.userapiclient, + lb_https, + self.public_ip.ipaddress.id, + networkid=self.network.id, + projectid=self.project.id + ) + lb_rule_https.assign(self.userapiclient, [self.vm_2]) + + command = "curl -L --connect-timeout 3 -k --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + # 4. add cert to LB with port 443 + lb_rule_https.assignCert(self.userapiclient, self.sslcert.id) + + # 5. verify the website (should get expected content) + command = "curl -L --connect-timeout 3 --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, None, 1) + + command = "curl -sL --connect-timeout 3 -k --resolve %s:443:%s https://%s/test.html" % (DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + command = "curl -sL --connect-timeout 3 --cacert %s --resolve %s:443:%s https://%s/test.html" % (FULL_CHAIN, DOMAIN, self.public_ip.ipaddress.ipaddress, DOMAIN) + self.wait_for_service_ready(command, CONTENT, 1) + + # 6. remove cert from LB with port 443 + lb_rule_https.removeCert(self.userapiclient) + + # 7. delete SSL certificate + self.sslcert.delete(self.userapiclient) diff --git a/test/integration/smoke/test_ssvm.py b/test/integration/smoke/test_ssvm.py index a610576100f7..ffcd717d135c 100644 --- a/test/integration/smoke/test_ssvm.py +++ b/test/integration/smoke/test_ssvm.py @@ -18,15 +18,14 @@ """ # Import Local Modules from marvin.cloudstackTestCase import cloudstackTestCase -from marvin.cloudstackAPI import (stopSystemVm, +from marvin.cloudstackAPI import (getDiagnosticsData, stopSystemVm, rebootSystemVm, destroySystemVm, updateConfiguration) from marvin.lib.utils import (cleanup_resources, get_process_status, get_host_credentials, wait_until) -from marvin.lib.base import (PhysicalNetwork, - NetScaler, ImageStore) +from marvin.lib.base import (PhysicalNetwork, NetScaler, ImageStore, UserData) from marvin.lib.common import (get_zone, list_hosts, list_ssvms, @@ -35,6 +34,10 @@ from nose.plugins.attrib import attr import telnetlib import logging +import base64 +import os +import urllib +import zipfile # Import System modules import time diff --git a/test/integration/smoke/test_systemvm_userdata.py b/test/integration/smoke/test_systemvm_userdata.py new file mode 100644 index 000000000000..e34ae53fcc75 --- /dev/null +++ b/test/integration/smoke/test_systemvm_userdata.py @@ -0,0 +1,410 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.cloudstackAPI import ( + getDiagnosticsData, + stopSystemVm, + rebootSystemVm, + destroySystemVm, + updateConfiguration, +) +from marvin.lib.utils import ( + cleanup_resources, + get_process_status, + get_host_credentials, + wait_until, +) +from marvin.lib.base import UserData, Network +from marvin.lib.common import ( + get_zone, + list_hosts, + list_routers, + list_ssvms, + list_zones, + list_vlan_ipranges, + createEnabledNetworkOffering, +) +from marvin.codes import PASS +from nose.plugins.attrib import attr +import telnetlib +import logging +import base64 +import os +import urllib +import zipfile +import uuid +import shutil + +# Import System modules +import time + +_multiprocess_shared_ = True + + +class TestSystemVMUserData(cloudstackTestCase): + @classmethod + def setUpClass(cls): + cls.testClient = super(TestSystemVMUserData, cls).getClsTestClient() + cls.api_client = cls.testClient.getApiClient() + + # Fill services from the external config file + cls.testData = cls.testClient.getParsedTestDataConfig() + + # Enable user data and set the script to be run on SSVM + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = "systemvm.userdata.enabled" + cmd.value = "true" + cls.api_client.updateConfiguration(cmd) + + @classmethod + def tearDownClass(cls): + # Disable user data + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = "systemvm.userdata.enabled" + cmd.value = "false" + cls.api_client.updateConfiguration(cmd) + + def setUp(self): + test_case = super(TestSystemVMUserData, self) + self.apiclient = self.testClient.getApiClient() + self.hypervisor = self.testClient.getHypervisorInfo() + self.cleanup = [] + self.config = test_case.getClsConfig() + self.services = self.testClient.getParsedTestDataConfig() + self.zone = get_zone(self.apiclient, self.testClient.getZoneForTests()) + + self.logger = logging.getLogger("TestSystemVMUserData") + self.stream_handler = logging.StreamHandler() + self.logger.setLevel(logging.DEBUG) + self.logger.addHandler(self.stream_handler) + + def tearDown(self): + if self.userdata_id: + UserData.delete(self.apiclient, self.userdata_id) + self.userdata_id = None + + try: + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + def waitForSystemVMAgent(self, vmname): + def checkRunningAgent(): + list_host_response = list_hosts(self.apiclient, name=vmname) + if isinstance(list_host_response, list): + return list_host_response[0].state == "Up", None + return False, None + + res, _ = wait_until(3, 300, checkRunningAgent) + if not res: + raise Exception("Failed to wait for SSVM agent to be Up") + + def checkForRunningSystemVM(self, ssvm, ssvm_type=None): + if not ssvm: + return None + + def checkRunningState(): + if not ssvm_type: + response = list_ssvms(self.apiclient, id=ssvm.id) + else: + response = list_ssvms( + self.apiclient, zoneid=self.zone.id, systemvmtype=ssvm_type + ) + + if isinstance(response, list): + ssvm_response = response[0] + return ssvm_response.state == "Running", ssvm_response + return False, None + + res, ssvm_response = wait_until(3, 300, checkRunningState) + if not res: + self.fail("Failed to reach systemvm state to Running") + return ssvm_response + + def register_userdata( + self, userdata_name, global_setting_name, vm_type_display_name + ): + """Helper method to register userdata and configure the global setting + + Args: + userdata_name: Name for the userdata entry + global_setting_name: Global setting name to update (e.g., 'secstorage.vm.userdata', 'console.proxy.vm.userdata', 'virtual.router.userdata') + vm_type_display_name: Display name for the VM type (e.g., 'SSVM', 'CPVM', 'VR') + + Returns: + UserData object + """ + userdata_script = f"""#!/bin/bash +echo "User data script ran successfully on {vm_type_display_name}" > /tmp/userdata.txt +""" + b64_encoded_userdata = base64.b64encode(userdata_script.encode("utf-8")).decode( + "utf-8" + ) + + # Create a userdata entry + try: + userdata = UserData.register( + self.apiclient, name=userdata_name, userdata=b64_encoded_userdata + ) + userdata_id = userdata["userdata"]["id"] + except Exception as e: + if "already exists" in str(e): + self.debug("Userdata already exists, getting it") + userdata = UserData.list( + self.apiclient, name=userdata_name, listall=True + )[0] + userdata_id = userdata.id + else: + self.fail("Failed to register userdata: %s" % e) + + # Update global configuration to use this userdata + cmd = updateConfiguration.updateConfigurationCmd() + cmd.name = global_setting_name + cmd.value = userdata_id + self.apiclient.updateConfiguration(cmd) + self.debug( + "Updated global setting %s with userdata ID: %s" + % (global_setting_name, userdata.id) + ) + + return userdata_id + + def download_and_verify_diagnostics_data( + self, target_id, vm_type_display_name, expected_content, retries=4 + ): + """Helper method to download and verify diagnostics data + + Args: + target_id: ID of the target VM/router + vm_type_display_name: Display name for log messages (e.g., 'SSVM', 'CPVM', 'VR') + expected_content: Expected content to verify in the userdata file + retries: Number of retries for getDiagnosticsData (default: 4) + """ + # Create a random temporary directory for this test + random_suffix = uuid.uuid4().hex[:8] + vm_type_prefix = vm_type_display_name.lower() + temp_dir = f"/tmp/{vm_type_prefix}-{random_suffix}" + os.makedirs(temp_dir, exist_ok=True) + + # Download the file created by userdata script using the getDiagnosticsData command + cmd = getDiagnosticsData.getDiagnosticsDataCmd() + cmd.targetid = target_id + cmd.files = "/tmp/userdata.txt" + + # getDiagnosticsData command takes some time to work after a VM is started + response = None + while retries > -1: + try: + response = self.apiclient.getDiagnosticsData(cmd) + break # Success, exit retry loop + except Exception as e: + if retries >= 0: + retries = retries - 1 + self.debug( + "getDiagnosticsData failed (retries left: %d): %s" + % (retries + 1, e) + ) + if retries > -1: + time.sleep(30) + continue + # If all retries exhausted, re-raise the exception + self.fail("Failed to get diagnostics data after retries: %s" % e) + + # Download response.url file to temporary directory and extract it + zip_file_path = os.path.join(temp_dir, "userdata.zip") + extracted_file_path = os.path.join(temp_dir, "userdata.txt") + + self.debug( + "Downloading userdata file from %s to %s" + % (vm_type_display_name, zip_file_path) + ) + try: + urllib.request.urlretrieve(response.url, zip_file_path) + except Exception as e: + self.fail( + "Failed to download userdata file from %s: %s" + % (vm_type_display_name, e) + ) + self.debug( + "Downloaded userdata file from %s: %s" + % (vm_type_display_name, zip_file_path) + ) + + try: + with zipfile.ZipFile(zip_file_path, "r") as zip_ref: + zip_ref.extractall(temp_dir) + except zipfile.BadZipFile as e: + self.fail("Downloaded userdata file is not a zip file: %s" % e) + self.debug("Extracted userdata file from zip: %s" % extracted_file_path) + + # Verify the file contains the expected content + try: + with open(extracted_file_path, "r") as f: + content = f.read().strip() + self.debug("Userdata file content: %s" % content) + self.assertEqual( + expected_content in content, + True, + f"Check that userdata file contains expected content: '{expected_content}'", + ) + except FileNotFoundError: + self.fail( + "Userdata file not found in extracted zip at %s" % extracted_file_path + ) + except Exception as e: + self.fail("Failed to read userdata file: %s" % e) + finally: + # Clean up temporary directory + try: + if os.path.exists(temp_dir): + shutil.rmtree(temp_dir) + self.debug("Cleaned up temporary directory: %s" % temp_dir) + except Exception as e: + self.debug( + "Failed to clean up temporary directory %s: %s" % (temp_dir, e) + ) + + def test_userdata_on_systemvm( + self, systemvm_type, userdata_name, vm_type_display_name, global_setting_name + ): + """Helper method to test user data functionality on system VMs + + Args: + systemvm_type: Type of system VM ('secondarystoragevm' or 'consoleproxy') + userdata_name: Name for the userdata entry + vm_type_display_name: Display name for log messages (e.g., 'SSVM' or 'CPVM') + global_setting_name: Global setting name for userdata (e.g., 'secstorage.vm.userdata' or 'console.proxy.vm.userdata') + """ + # 1) Register userdata and configure global setting + self.userdata_id = self.register_userdata( + userdata_name, global_setting_name, vm_type_display_name + ) + + # 2) Get and destroy the system VM to trigger recreation with userdata + list_ssvm_response = list_ssvms( + self.apiclient, + systemvmtype=systemvm_type, + state="Running", + zoneid=self.zone.id, + ) + self.assertEqual( + isinstance(list_ssvm_response, list), + True, + "Check list response returns a valid list", + ) + + ssvm = list_ssvm_response[0] + self.debug("Destroying %s: %s" % (vm_type_display_name, ssvm.id)) + cmd = destroySystemVm.destroySystemVmCmd() + cmd.id = ssvm.id + self.apiclient.destroySystemVm(cmd) + + # 3) Wait for the system VM to be running again + ssvm_response = self.checkForRunningSystemVM(ssvm, systemvm_type) + self.debug( + "%s state after restart: %s" % (vm_type_display_name, ssvm_response.state) + ) + self.assertEqual( + ssvm_response.state, + "Running", + "Check whether %s is running or not" % vm_type_display_name, + ) + # Wait for the agent to be up + self.waitForSystemVMAgent(ssvm_response.name) + + # 4) Download and verify the diagnostics data + expected_content = ( + f"User data script ran successfully on {vm_type_display_name}" + ) + self.download_and_verify_diagnostics_data( + ssvm_response.id, vm_type_display_name, expected_content + ) + + @attr( + tags=["advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="true", + ) + def test_1_userdata_on_ssvm(self): + """Test user data functionality on SSVM""" + self.test_userdata_on_systemvm( + systemvm_type="secondarystoragevm", + userdata_name="ssvm_userdata", + vm_type_display_name="SSVM", + global_setting_name="secstorage.vm.userdata", + ) + + @attr( + tags=["advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="true", + ) + def test_2_userdata_on_cpvm(self): + """Test user data functionality on CPVM""" + self.test_userdata_on_systemvm( + systemvm_type="consoleproxy", + userdata_name="cpvm_userdata", + vm_type_display_name="CPVM", + global_setting_name="console.proxy.vm.userdata", + ) + + @attr( + tags=["advanced", "advancedns", "smoke", "basic", "sg"], + required_hardware="true", + ) + def test_3_userdata_on_vr(self): + """Test user data functionality on VR""" + # 1) Register userdata and configure global setting + self.userdata_id = self.register_userdata("vr_userdata", "virtual.router.userdata", "VR") + + # 2) Create an isolated network which will trigger VR creation with userdata + result = createEnabledNetworkOffering( + self.apiclient, self.testData["nw_off_isolated_persistent"] + ) + assert result[0] == PASS, ( + "Network offering creation/enabling failed due to %s" % result[2] + ) + isolated_persistent_network_offering = result[1] + + # Create an isolated network + self.network = Network.create( + self.apiclient, + self.testData["isolated_network"], + networkofferingid=isolated_persistent_network_offering.id, + zoneid=self.zone.id, + ) + self.assertIsNotNone(self.network, "Network creation failed") + self.cleanup.append(self.network) + self.cleanup.append(isolated_persistent_network_offering) + + # 3) Get the VR and verify it's running + routers = list_routers( + self.apiclient, networkid=self.network.id, state="Running" + ) + self.assertEqual( + isinstance(routers, list), + True, + "Check list router response returns a valid list", + ) + self.assertNotEqual(len(routers), 0, "Check list router response") + router = routers[0] + self.debug("Found VR: %s" % router.id) + + # 4) Download and verify the diagnostics data + # VR doesn't need retries as it's freshly created + expected_content = "User data script ran successfully on VR" + self.download_and_verify_diagnostics_data(router.id, "VR", expected_content) diff --git a/test/integration/smoke/test_usage.py b/test/integration/smoke/test_usage.py index 9ec5205403e1..fef0d8fe3c1e 100644 --- a/test/integration/smoke/test_usage.py +++ b/test/integration/smoke/test_usage.py @@ -1751,6 +1751,14 @@ def setUpClass(cls): domainid=cls.virtual_machine.domainid, services=cls.services["server"] ) + src_nat_list = PublicIPAddress.list( + cls.api_client, + accountid=cls.virtual_machine.account, + zoneid=cls.virtual_machine.zoneid, + domainid=cls.virtual_machine.domainid, + issourcenat=True + ) + cls.public_ip = src_nat_list[0] return @classmethod @@ -1789,11 +1797,11 @@ def test_01_vpn_usage(self): # 4. Delete this account. self.debug("Created VPN with public IP: %s" % - self.public_ip.ipaddress.id) + self.public_ip.ipaddress) # Assign VPN to Public IP vpn = Vpn.create( self.apiclient, - self.public_ip.ipaddress.id, + self.public_ip.id, account=self.account.name, domainid=self.account.domainid ) diff --git a/test/integration/smoke/test_vm_autoscaling.py b/test/integration/smoke/test_vm_autoscaling.py index 782d2bce3ad2..dd507ece0391 100644 --- a/test/integration/smoke/test_vm_autoscaling.py +++ b/test/integration/smoke/test_vm_autoscaling.py @@ -38,6 +38,7 @@ DiskOffering, Domain, Project, + ResourceSchedule, ServiceOffering, Template, VirtualMachine, @@ -65,6 +66,7 @@ DEFAULT_INTERVAL = 30 DEFAULT_QUIETTIME = 60 NAME_PREFIX = "AS-VmGroup-" +SCHEDULED_RESOURCE_TYPE_ASG = "AutoScaleVmGroup" CONFIG_NAME_DISK_CONTROLLER = "vmware.root.disk.controller" OS_DEFAULT = "osdefault" @@ -860,13 +862,81 @@ def test_04_stop_remove_vm_in_vmgroup(self): VirtualMachine.delete(vm, self.apiclient, expunge=True) - @attr(tags=["advanced"], required_hardware="false") - def test_05_remove_vmgroup(self): - """ Verify removal of AutoScaling VM Group""" - self.message("Running test_05_remove_vmgroup") + @attr(tags=["advanced", "vj", "smoke"], required_hardware="false") + def test_05_autoscaling_vmgroup_schedule_update(self): + """Verify resource scheduling updates AutoScale VM Group min/max members""" + self.message("Running test_05_autoscaling_vmgroup_schedule_update") - self.delete_vmgroup(self.autoscaling_vmgroup, self.regular_user_apiclient, cleanup=False, expected=False) - self.delete_vmgroup(self.autoscaling_vmgroup, self.regular_user_apiclient, cleanup=True, expected=True) + vmgroups = AutoScaleVmGroup.list( + self.regular_user_apiclient, + id=self.autoscaling_vmgroup.id + ) + self.assertEqual( + isinstance(vmgroups, list), + True, + "List autoscale vm groups should return a valid list" + ) + self.assertEqual( + len(vmgroups) > 0, + True, + "Expected autoscale vm group to exist" + ) + vmgroup = vmgroups[0] + + current_min_members = int(vmgroup.minmembers) + current_max_members = int(vmgroup.maxmembers) + new_min_members = current_min_members + 1 + new_max_members = current_max_members + 1 + + schedule = ResourceSchedule.create( + self.regular_user_apiclient, + SCHEDULED_RESOURCE_TYPE_ASG, + self.autoscaling_vmgroup.id, + "update", + "* * * * *", + datetime.datetime.now().astimezone().tzinfo, + (datetime.datetime.now() + datetime.timedelta(seconds=5)).strftime( + "%Y-%m-%d %H:%M:%S" + ), + enabled=True, + details=[{ + "minmembers": str(new_min_members)}, + { + "maxmembers": str(new_max_members) + }] + ) + self.cleanup.append(schedule) + self.message("Created AutoScale VM Group schedule with ID: %s" % schedule.id) + + schedules = ResourceSchedule.list( + self.regular_user_apiclient, + SCHEDULED_RESOURCE_TYPE_ASG, + self.autoscaling_vmgroup.id, + id=schedule.id + ) + self.assertEqual( + isinstance(schedules, list), + True, + "Check list response returns a valid list for ASG schedules", + ) + self.assertNotEqual(len(schedules), 0, "Check ASG schedule list") + + # Poll for up to 4 minutes to allow the scheduler worker to run and apply update + schedule_worked = False + for _ in range(0, 24): + time.sleep(10) + updated_group = AutoScaleVmGroup.list( + self.regular_user_apiclient, + id=self.autoscaling_vmgroup.id + )[0] + if int(updated_group.minmembers) == new_min_members and int(updated_group.maxmembers) == new_max_members: + schedule_worked = True + break + + self.assertTrue( + schedule_worked, + "Expected scheduled action to update AutoScale VM Group min/max members" + ) @attr(tags=["advanced"], required_hardware="false") def test_06_autoscaling_vmgroup_on_project_network(self): @@ -1211,3 +1281,11 @@ def test_07_autoscaling_vmgroup_on_vpc_network(self): self.delete_vmgroup(autoscaling_vmgroup_vpc, self.regular_user_apiclient, cleanup=False, expected=False) self.delete_vmgroup(autoscaling_vmgroup_vpc, self.regular_user_apiclient, cleanup=True, expected=True) + + @attr(tags=["advanced", "vj"], required_hardware="false") + def test_05_remove_vmgroup(self): + """ Verify removal of AutoScaling VM Group""" + self.message("Running test_05_remove_vmgroup") + + self.delete_vmgroup(self.autoscaling_vmgroup, self.regular_user_apiclient, cleanup=False, expected=False) + self.delete_vmgroup(self.autoscaling_vmgroup, self.regular_user_apiclient, cleanup=True, expected=True) diff --git a/test/integration/smoke/test_vm_lifecycle_unmanage_kvm_import.py b/test/integration/smoke/test_vm_lifecycle_unmanage_kvm_import.py new file mode 100644 index 000000000000..79c7513f2e2b --- /dev/null +++ b/test/integration/smoke/test_vm_lifecycle_unmanage_kvm_import.py @@ -0,0 +1,473 @@ +# 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. +""" BVT tests for Virtual Machine Life Cycle - Unmanage - Import +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.utils import * +from marvin.lib.base import (Account, + ServiceOffering, + VirtualMachine, + Host, + Network, + NetworkOffering, + VirtualMachine) +from marvin.lib.common import (get_domain, + get_zone, + get_suitable_test_template) +from marvin.codes import FAILED +from nose.plugins.attrib import attr +from marvin.lib.decoratorGenerators import skipTestIf +import unittest +from marvin.sshClient import SshClient + +_multiprocess_shared_ = True + +class TestUnmanageKvmVM(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestUnmanageKvmVM, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + cls.hypervisor = testClient.getHypervisorInfo() + cls._cleanup = [] + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.template = get_suitable_test_template( + cls.apiclient, + cls.zone.id, + cls.services["ostype"], + cls.hypervisor + ) + if cls.template == FAILED: + assert False, "get_suitable_test_template() failed to return template with description %s" % cls.services["ostype"] + + cls.hypervisorNotSupported = cls.hypervisor.lower() != "kvm" + if cls.hypervisorNotSupported: + return + + cls.services["small"]["zoneid"] = cls.zone.id + cls.services["small"]["template"] = cls.template.id + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + cls.small_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["small"] + ) + cls._cleanup.append(cls.small_offering) + + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["l2-network_offering"], + ) + cls._cleanup.append(cls.network_offering) + cls.network_offering.update(cls.apiclient, state='Enabled') + cls.isolated_network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["nw_off_isolated_persistent"], + ) + cls._cleanup.append(cls.isolated_network_offering) + cls.isolated_network_offering.update(cls.apiclient, state='Enabled') + + @classmethod + def tearDownClass(cls): + super(TestUnmanageKvmVM, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + self.created_networks = [] + self.virtual_machine = None + self.unmanaged_instance = None + self.imported_vm = None + self.hostConfig = self.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ + + if self.hypervisorNotSupported: + return + self.services["network"]["networkoffering"] = self.network_offering.id + network_data = self.services["l2-network"] + self.network = Network.create( + self.apiclient, + network_data, + zoneid=self.zone.id, + networkofferingid=self.network_offering.id + ) + self.cleanup.append(self.network) + self.created_networks.append(self.network) + network_data['name'] = "Test L2 Network1" + network_data['displaytext'] = "Test L2 Network1" + self.network1 = Network.create( + self.apiclient, + network_data, + zoneid=self.zone.id, + networkofferingid=self.network_offering.id + ) + self.cleanup.append(self.network1) + self.created_networks.append(self.network1) + self.network2 = Network.create( + self.apiclient, + self.services["isolated_network"], + zoneid=self.zone.id, + networkofferingid=self.isolated_network_offering.id + ) + self.cleanup.append(self.network2) + self.created_networks.append(self.network2) + + + def tearDown(self): + super(TestUnmanageKvmVM, self).tearDown() + + def check_vm_state(self, vm_id): + list_vm = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + self.assertEqual( + isinstance(list_vm, list), + True, + "Check if virtual machine is present" + ) + vm_response = list_vm[0] + self.assertEqual( + vm_response.state, + "Running", + "VM state should be running after deployment" + ) + return vm_response + + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_01_unmanage_vm_cycle_persistent_domain(self): + """ + Test the following: + 1. Deploy VM + 2. Unmanage VM + 3. Verify VM is not listed in CloudStack + 4. Verify VM is listed as part of the unmanaged instances + 5. Verify VM domain is persistent for KVM hypervisor + 6. Stop VM using virsh, confirm VM is stopped + 7. Start VM using virsh, confirm VM is running + 8. Import VM + 9. Verify details of imported VM + 10. Destroy VM + """ + + # 1 - Deploy VM + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.small_offering.id, + networkids=[self.network.id, self.network1.id, self.network2.id], + zoneid=self.zone.id + ) + + vm_id = self.virtual_machine.id + vm_instance_name = self.virtual_machine.instancename + + networks = [] + for network in self.created_networks: + n = Network.list( + self.apiclient, + id=network.id + )[0] + networks.append(n) + hostid = self.virtual_machine.hostid + hosts = Host.list( + self.apiclient, + id=hostid + ) + host = hosts[0] + clusterid = host.clusterid + self.check_vm_state(vm_id) + + # 2 - Unmanage VM from CloudStack + self.virtual_machine.unmanage(self.apiclient) + + # 3 - Verify VM is not listed in CloudStack + list_vm = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + self.assertEqual( + list_vm, + None, + "VM should not be listed" + ) + # 4 - Verify VM is listed as part of the unmanaged instances + unmanaged_vms = VirtualMachine.listUnmanagedInstances( + self.apiclient, + clusterid=clusterid, + name=vm_instance_name + ) + + self.assertEqual( + len(unmanaged_vms), + 1, + "Unmanaged VMs matching instance name list size is 1" + ) + unmanaged_vm = unmanaged_vms[0] + self.assertEqual( + unmanaged_vm.powerstate, + "PowerOn", + "Unmanaged VM is still running" + ) + + # 5 - Verify VM domain is persistent for KVM + ssh_host = self.get_ssh_client(host.ipaddress, + self.hostConfig["username"], + self.hostConfig["password"], 10) + + cmd = f"virsh dominfo {vm_instance_name}" + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception(f"Failed to fetch domain info for VM: {vm_instance_name} on host: {host.name}. Error: {result}") + persistent_line = next((line for line in result if "Persistent:" in line), None) + if not persistent_line: + raise Exception(f"'Persistent' info not found in dominfo output for VM: {vm_instance_name} on host: {host.name}") + if "yes" not in persistent_line.lower(): + raise Exception(f"VM: {vm_instance_name} is NOT persistent on host: {host.name}") + + + # 6 - Stop VM using virsh, confirm VM is stopped + host = Host.list( + self.apiclient, + id=unmanaged_vm.hostid + )[0] + cmd = "virsh destroy %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to stop VM: %s on host: %s" % (vm_instance_name, host.name)) + + cmd = "virsh list --all | grep %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to fetch VM: %s state on host: %s" % (vm_instance_name, host.name)) + state_line = next((line for line in result if vm_instance_name in line), None) + if not state_line: + raise Exception(f"VM: {vm_instance_name} not found in 'virsh list --all' output on host: {host.name}") + if 'shut off' not in state_line.lower(): + raise Exception(f"VM: {vm_instance_name} is NOT stopped on host: {host.name}, state: {state_line}") + + # 7 - Start VM using virsh, confirm VM is running + cmd = "virsh start %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to start VM: %s on host: %s" % (vm_instance_name, host.name)) + time.sleep(30) + cmd = "virsh list | grep %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to fetch VM: %s state on host: %s" % (vm_instance_name, host.name)) + if 'running' not in str(result).lower(): + raise Exception(f"VM: {vm_instance_name} is NOT running on host: {host.name}, state: {state_line}") + + # 8 - Import VM + + nicnetworklist = [] + nicipaddresslist = [] + for nic in unmanaged_vm.nic: + for network in networks: + if int(network.vlan) == int(nic.vlanid): + nicnetworklist.append({ + "nic": nic.id, + "network": network.id + }) + if network.type == "Isolated": + nicipaddresslist.append({ + "nic": nic.id, + "ip4Address": "auto" + }) + import_vm_service = { + "nicnetworklist": nicnetworklist, + "nicipaddresslist": nicipaddresslist + } + + self.imported_vm = VirtualMachine.importUnmanagedInstance( + self.apiclient, + clusterid=clusterid, + name=vm_instance_name, + serviceofferingid=self.small_offering.id, + services=import_vm_service, + templateid=self.template.id) + self.cleanup.append(self.imported_vm) + self.unmanaged_instance = None + # 9 - Verify details of the imported VM + self.assertEqual( + self.small_offering.id, + self.imported_vm.serviceofferingid, + "Imported VM service offering is different, expected: %s, actual: %s" % (self.small_offering.id, self.imported_vm.serviceofferingid) + ) + self.assertEqual( + self.template.id, + self.imported_vm.templateid, + "Imported VM template is different, expected: %s, actual: %s" % (self.template.id, self.imported_vm.templateid) + ) + self.check_vm_state(self.imported_vm.id) + # 10 - Destroy VM. This will be done during cleanup + + + + @attr(tags=["advanced", "advancedns", "smoke", "sg"], required_hardware="true") + @skipTestIf("hypervisorNotSupported") + def test_02_unmanage_stopped_vm_cycle_persistent_domain(self): + """ + Test the following: + 1. Deploy VM + 2. Stop VM + 3. Unmanage VM + 4. Verify VM is not listed in CloudStack + 5. Verify VM is listed as part of the unmanaged instances + 6. Start VM using virsh, confirm VM is running + 7. Import VM + 8. Verify details of imported VM + 9. Destroy VM + """ + # 1 - Deploy VM + self.virtual_machine = VirtualMachine.create( + self.apiclient, + self.services["virtual_machine"], + templateid=self.template.id, + serviceofferingid=self.small_offering.id, + networkids=[self.network.id, self.network1.id, self.network2.id], + zoneid=self.zone.id + ) + + vm_id = self.virtual_machine.id + vm_instance_name = self.virtual_machine.instancename + + networks = [] + for network in self.created_networks: + n = Network.list( + self.apiclient, + id=network.id + )[0] + networks.append(n) + hostid = self.virtual_machine.hostid + hosts = Host.list( + self.apiclient, + id=hostid + ) + host = hosts[0] + clusterid = host.clusterid + self.check_vm_state(vm_id) + + #2 - Stop VM + self.virtual_machine.stop(self.apiclient) + list_vm = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + self.assertEqual( + isinstance(list_vm, list), + True, + "Check if virtual machine is present" + ) + vm_response = list_vm[0] + self.assertEqual( + vm_response.state, + "Stopped", + "VM state should be Stopped after stopping the VM" + ) + + # 3 - Unmanage VM from CloudStack + self.virtual_machine.unmanage(self.apiclient) + + # 4 - Verify VM is not listed in CloudStack + list_vm = VirtualMachine.list( + self.apiclient, + id=vm_id + ) + self.assertEqual( + list_vm, + None, + "VM should not be listed" + ) + + # 5 - Verify VM is listed as part of the unmanaged instances + ssh_host = self.get_ssh_client(host.ipaddress, + self.hostConfig["username"], + self.hostConfig["password"], 10) + cmd = "virsh list --all | grep %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to fetch VM: %s state on host: %s" % (vm_instance_name, host.name)) + if 'shut off' not in str(result).lower(): + raise Exception(f"VM: {vm_instance_name} is NOT in stopped on host: {host.name}") + + # 6 - Start VM using virsh, confirm VM is running + cmd = "virsh start %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to start VM: %s on host: %s" % (vm_instance_name, host.name)) + time.sleep(30) + cmd = "virsh list | grep %s" % vm_instance_name + result = ssh_host.execute(cmd) + if result == None or result == "": + raise Exception("Failed to fetch VM: %s state on host: %s" % (vm_instance_name, host.name)) + if 'running' not in str(result).lower(): + raise Exception(f"VM: {vm_instance_name} is NOT running on host: {host.name}") + + # 8 - Import VM + self.imported_vm = VirtualMachine.importUnmanagedInstance( + self.apiclient, + clusterid=clusterid, + name=vm_instance_name, + serviceofferingid=self.small_offering.id, + services={}, + templateid=self.template.id) + self.cleanup.append(self.imported_vm) + self.unmanaged_instance = None + + # 9 - Verify details of the imported VM + self.assertEqual( + self.small_offering.id, + self.imported_vm.serviceofferingid, + "Imported VM service offering is different, expected: %s, actual: %s" % (self.small_offering.id, self.imported_vm.serviceofferingid) + ) + self.assertEqual( + self.template.id, + self.imported_vm.templateid, + "Imported VM template is different, expected: %s, actual: %s" % (self.template.id, self.imported_vm.templateid) + ) + self.check_vm_state(self.imported_vm.id) + # 10 - Destroy VM. This will be done during cleanup + + + def get_ssh_client(self, ip, username, password, retries=10): + """ Setup ssh client connection and return connection """ + try: + ssh_client = SshClient(ip, 22, username, password, retries) + except Exception as e: + raise unittest.SkipTest("Unable to create ssh connection: " % e) + + self.assertIsNotNone( + ssh_client, "Failed to setup ssh connection to ip=%s" % ip) + + return ssh_client diff --git a/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py new file mode 100644 index 000000000000..871cf25a4021 --- /dev/null +++ b/test/integration/smoke/test_vm_lifecycle_with_snapshot_or_volume.py @@ -0,0 +1,318 @@ +# 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. +""" BVT tests for Virtual Machine Life Cycle +""" +# Import Local Modules +from marvin.cloudstackTestCase import cloudstackTestCase + +from marvin.lib.utils import * + +from marvin.lib.base import (Account, + Role, + ServiceOffering, + VirtualMachine, + Host, + StoragePool, + Volume, + DiskOffering, + Snapshot, + Template) +from marvin.lib.common import (get_domain, + get_zone, + get_template, + list_hosts, + list_volumes, + list_storage_pools) +from marvin.codes import FAILED, PASS +from nose.plugins.attrib import attr + +import uuid +import unittest + +class TestDeployVMFromSnapshotOrVolume(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestDeployVMFromSnapshotOrVolume, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.services = testClient.getParsedTestDataConfig() + + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.services['mode'] = cls.zone.networktype + cls.hypervisor = testClient.getHypervisorInfo() + + if cls.hypervisor.lower() != "kvm": + raise unittest.SkipTest("Only KVM hypervisor is supported for deployment of a VM with volume/snapshot") + + cls.template = get_template( + cls.apiclient, + cls.zone.id, + account="system" + ) + if cls.template == FAILED: + assert False, "get_template failed to return template with description [system]" + + cls.services["small"]["zoneid"] = cls.zone.id + + cls._cleanup = [] + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + cls.debug(cls.account.id) + + storage_pools_response = list_storage_pools(cls.apiclient, + zoneid=cls.zone.id, + scope="ZONE") + + if storage_pools_response: + cls.zone_wide_storage = storage_pools_response[0] + + cls.debug( + "zone wide storage id is %s" % + cls.zone_wide_storage.id) + update1 = StoragePool.update(cls.apiclient, + id=cls.zone_wide_storage.id, + tags="test-vm" + ) + cls.debug( + "Storage %s pool tag%s" % + (cls.zone_wide_storage.id, update1.tags)) + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["small"], + tags="test-vm" + ) + cls._cleanup.append(cls.service_offering) + + do = { + "name": "do-tags", + "displaytext": "Disk offering with tags", + "disksize":8, + "tags": "test-vm" + } + cls.disk_offering = DiskOffering.create( + cls.apiclient, + do, + ) + cls._cleanup.append(cls.disk_offering) + else: + raise unittest.SkipTest("No zone wide storage found. Skipping tests") + + + cls.virtual_machine = VirtualMachine.create( + cls.apiclient, + cls.services["small"], + accountid=cls.account.name, + domainid=cls.account.domainid, + templateid=cls.template.id, + serviceofferingid=cls.service_offering.id, + mode=cls.services['mode'] + ) + volume = list_volumes( + cls.apiclient, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + )[0] + cls.snapshot = Snapshot.create( + cls.apiclient, + volume.id, + ) + + @classmethod + def tearDownClass(cls): + super(TestDeployVMFromSnapshotOrVolume, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + + def tearDown(self): + super(TestDeployVMFromSnapshotOrVolume, self).tearDown() + + @attr(tags=["advanced"], required_hardware="false") + def test_01_deploy_vm_with_existing_volume(self): + ''' + Deploy a Virtual machine with existing volume + ''' + self.create_volume_from_snapshot_deploy_vm(self.snapshot.id) + + @attr(tags=["advanced"], required_hardware="false") + def test_02_deploy_vm_with_existing_snapshot(self): + ''' + Deploy a Virtual machine with existing snapshot + ''' + + self.deploy_vm_from_snapshot(self.snapshot) + + @attr(tags=["advanced"], required_hardware="false") + def test_03_deploy_vm_with_existing_volume_deleted_template(self): + ''' + Deploy a Virtual machine with existing ROOT volume created from a templated which was deleted + ''' + services = {"displaytext": "Template-1", "name": "Template-1-name", "ostypeid": self.template.ostypeid, + "ispublic": "true"} + + template = Template.create_from_snapshot(self.apiclient, self.snapshot, services) + self._cleanup.append(template) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + templateid=template.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + root_volume = list_volumes( + self.apiclient, + virtualmachineid=virtual_machine.id, + type='ROOT', + listall=True + )[0] + snapshot = Snapshot.create( + self.apiclient, + root_volume.id, + ) + VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) + self.create_volume_from_snapshot_deploy_vm(snapshot.id) + + @attr(tags=["advanced"], required_hardware="false") + def test_04_deploy_vm_with_existing_snapshot_deleted_template(self): + ''' + Deploy a Virtual machine with existing snapshot of a ROOT volume created from a templated which was deleted + ''' + services = {"displaytext": "Template-1", "name": "Template-1-name", "ostypeid": self.template.ostypeid, + "ispublic": "true"} + + template = Template.create_from_snapshot(self.apiclient, self.snapshot, services) + self._cleanup.append(template) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + templateid=template.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + root_volume = list_volumes( + self.apiclient, + virtualmachineid=virtual_machine.id, + type='ROOT', + listall=True + )[0] + snapshot = Snapshot.create( + self.apiclient, + root_volume.id, + ) + VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) + self.deploy_vm_from_snapshot(snapshot) + + @attr(tags=["advanced"], required_hardware="false") + def test_05_deploy_vm_with_existing_snapshot_deleted_volume(self): + ''' + Deploy a Virtual machine with existing snapshot of a ROOT volume which was deleted + ''' + + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + templateid=self.template.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + root_volume = list_volumes( + self.apiclient, + virtualmachineid=virtual_machine.id, + type='ROOT', + listall=True + )[0] + snapshot = Snapshot.create( + self.apiclient, + root_volume.id, + ) + VirtualMachine.delete(virtual_machine, self.apiclient, expunge=True) + self.deploy_vm_from_snapshot(snapshot) + + def deploy_vm_from_snapshot(self, snapshot): + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + snapshotid=snapshot.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) + + def create_volume_from_snapshot_deploy_vm(self, snapshotid): + volume = Volume.create_from_snapshot( + self.apiclient, + snapshot_id=snapshotid, + services=self.services, + disk_offering=self.disk_offering.id, + account=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + ) + virtual_machine = VirtualMachine.create(self.apiclient, + {"name": "Test-%s" % uuid.uuid4()}, + accountid=self.account.name, + domainid=self.account.domainid, + zoneid=self.zone.id, + serviceofferingid=self.service_offering.id, + volumeid=volume.id, + mode="basic", + ) + try: + ssh_client = virtual_machine.get_ssh_client() + except Exception as e: + self.fail("SSH failed for virtual machine: %s - %s" % + (virtual_machine.ipaddress, e)) diff --git a/test/integration/smoke/test_vm_schedule.py b/test/integration/smoke/test_vm_schedule.py index ee1354ff80ee..1e476c55fc87 100644 --- a/test/integration/smoke/test_vm_schedule.py +++ b/test/integration/smoke/test_vm_schedule.py @@ -17,7 +17,7 @@ """ P1 tests for VM Schedule """ from marvin.cloudstackTestCase import cloudstackTestCase -from marvin.lib.base import Account, ServiceOffering, VirtualMachine, VMSchedule +from marvin.lib.base import Account, ServiceOffering, VirtualMachine, ResourceSchedule from marvin.lib.common import get_domain, get_zone, get_template from marvin.lib.utils import cleanup_resources @@ -28,6 +28,8 @@ import time +RESOURCE_TYPE = "VirtualMachine" + class Services: """Test Snapshots Services""" @@ -148,8 +150,9 @@ def test_01_vmschedule_create(self): # Create VM Schedule schedule = "0 0 1 * *" - vmschedule = VMSchedule.create( + vmschedule = ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", schedule, @@ -166,8 +169,8 @@ def test_01_vmschedule_create(self): self.debug("Created VM Schedule with ID: %s" % vmschedule.id) # List VM Schedule - vmschedules = VMSchedule.list( - self.apiclient, self.virtual_machine.id, id=vmschedule.id + vmschedules = ResourceSchedule.list( + self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=vmschedule.id ) self.assertEqual( @@ -187,9 +190,15 @@ def test_01_vmschedule_create(self): ) self.assertEqual( - vmschedules[0].virtualmachineid, + vmschedules[0].resourceid, self.virtual_machine.id, - "Check VM ID in list resources call", + "Check resource ID in list resources call", + ) + + self.assertEqual( + vmschedules[0].resourcetype, + RESOURCE_TYPE, + "Check resource type in list resources call", ) self.assertEqual( @@ -204,9 +213,9 @@ def test_01_vmschedule_create(self): "Check VM Schedule timezone in list resources call", ) - # Check for entry in vm_scheduled_job in db + # Check for entry in resource_scheduled_job in db vmscheduled_job = self.dbclient.execute( - "select * from vm_scheduled_job where vm_schedule_id IN (SELECT id FROM vm_schedule WHERE uuid = '%s')" + "select * from resource_scheduled_job where schedule_id IN (SELECT id FROM resource_schedule WHERE uuid = '%s')" % vmschedule.id, db="cloud", ) @@ -214,13 +223,13 @@ def test_01_vmschedule_create(self): self.assertIsInstance( vmscheduled_job, list, - "Check if VM Schedule exists in vm_scheduled_job table", + "Check if VM Schedule exists in resource_scheduled_job table", ) self.assertGreater( len(vmscheduled_job), 0, - "Check if VM Schedule exists in vm_scheduled_job table", + "Check if VM Schedule exists in resource_scheduled_job table", ) return @@ -237,8 +246,9 @@ def test_02_vmschedule_create_parameter_exceptions(self): # Create VM Schedule with invalid virtual machine ID with self.assertRaises(Exception): - VMSchedule.create( + ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, "invalid", "start", "0 0 1 * *", @@ -251,8 +261,9 @@ def test_02_vmschedule_create_parameter_exceptions(self): # Create VM Schedule with invalid schedule with self.assertRaises(Exception): - VMSchedule.create( + ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", "invalid", @@ -265,8 +276,9 @@ def test_02_vmschedule_create_parameter_exceptions(self): # Create VM Schedule with invalid start date with self.assertRaises(Exception): - VMSchedule.create( + ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", "0 0 1 * *", @@ -277,8 +289,9 @@ def test_02_vmschedule_create_parameter_exceptions(self): # Create VM Schedule with invalid action with self.assertRaises(Exception): - VMSchedule.create( + ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "invalid", "0 0 1 * *", @@ -291,8 +304,9 @@ def test_02_vmschedule_create_parameter_exceptions(self): # test invalid end date with self.assertRaises(Exception): - VMSchedule.create( + ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", "0 0 1 * *", @@ -315,8 +329,9 @@ def test_03_vmschedule_update(self): # Create VM Schedule schedule = "0 0 1 * *" - vmschedule = VMSchedule.create( + vmschedule = ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", schedule, @@ -351,8 +366,8 @@ def test_03_vmschedule_update(self): self.debug("Updated VM Schedule with ID: %s" % vmschedule.id) # List VM Schedule - vmschedules = VMSchedule.list( - self.apiclient, self.virtual_machine.id, id=vmschedule.id + vmschedules = ResourceSchedule.list( + self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=vmschedule.id ) self.assertEqual( isinstance(vmschedules, list), @@ -371,11 +386,17 @@ def test_03_vmschedule_update(self): ) self.assertEqual( - vmschedules[0].virtualmachineid, + vmschedules[0].resourceid, self.virtual_machine.id, "Check VM ID in list resources call", ) + self.assertEqual( + vmschedules[0].resourcetype, + RESOURCE_TYPE, + "Check VM ID in list resources call", + ) + self.assertEqual( vmschedules[0].schedule, new_schedule, @@ -395,8 +416,9 @@ def test_04_vmschedule_update_parameter_exceptions(self): # Create VM Schedule schedule = "0 0 1 * *" - vmschedule = VMSchedule.create( + vmschedule = ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", schedule, @@ -486,8 +508,9 @@ def test_05_vmschedule_test_e2e(self): # Create VM Schedule - start start_schedule = "*/2 * * * *" - start_vmschedule = VMSchedule.create( + start_vmschedule = ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "start", start_schedule, @@ -503,8 +526,9 @@ def test_05_vmschedule_test_e2e(self): # Create VM Schedule - stop stop_schedule = "*/1 * * * *" - stop_vmschedule = VMSchedule.create( + stop_vmschedule = ResourceSchedule.create( self.apiclient, + RESOURCE_TYPE, self.virtual_machine.id, "stop", stop_schedule, @@ -519,8 +543,8 @@ def test_05_vmschedule_test_e2e(self): self.debug("Created VM Schedule with ID: %s" % stop_vmschedule.id) # Verify VM Schedule is created - vmschedules = VMSchedule.list( - self.apiclient, self.virtual_machine.id, id=start_vmschedule.id + vmschedules = ResourceSchedule.list( + self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=start_vmschedule.id ) self.assertEqual( @@ -575,15 +599,15 @@ def test_05_vmschedule_test_e2e(self): # Verify VM Schedule is deleted self.assertEqual( - VMSchedule.list( - self.apiclient, self.virtual_machine.id, id=start_vmschedule.id + ResourceSchedule.list( + self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=start_vmschedule.id ), None, "Check VM Schedule is deleted", ) self.assertEqual( - VMSchedule.list( - self.apiclient, self.virtual_machine.id, id=stop_vmschedule.id + ResourceSchedule.list( + self.apiclient, RESOURCE_TYPE, self.virtual_machine.id, id=stop_vmschedule.id ), None, "Check VM Schedule is deleted", diff --git a/test/integration/smoke/test_vm_snapshots.py b/test/integration/smoke/test_vm_snapshots.py index 8c106f05a9f6..c64de12b1c31 100644 --- a/test/integration/smoke/test_vm_snapshots.py +++ b/test/integration/smoke/test_vm_snapshots.py @@ -262,7 +262,7 @@ def test_02_revert_vm_snapshots(self): self.assertEqual( self.random_data_0, result[0], - "Check the random data is equal with the ramdom file!" + "Check the random data is equal with the random file!" ) @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") 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/test/integration/smoke/test_volumes.py b/test/integration/smoke/test_volumes.py index 6cf3f082bc22..ad1c359c27b5 100644 --- a/test/integration/smoke/test_volumes.py +++ b/test/integration/smoke/test_volumes.py @@ -988,7 +988,7 @@ def checkVolumeResponse(): return True, list_volume_response[0] return False, None - # sleep interval is 1s, retries is 360, this will sleep atmost 360 seconds, or 6 mins + # sleep interval is 1s, retries is 360, this will sleep at most 360 seconds, or 6 mins res, response = wait_until(1, 360, checkVolumeResponse) if not res: self.fail("Failed to return root volume response") diff --git a/test/integration/smoke/test_vpc_conserve_mode.py b/test/integration/smoke/test_vpc_conserve_mode.py new file mode 100644 index 000000000000..a56953db7872 --- /dev/null +++ b/test/integration/smoke/test_vpc_conserve_mode.py @@ -0,0 +1,314 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Tests for VPC Conserve Mode (since 4.23.0) + +Conserve mode allows public IP services (LB, Port Forwarding, Static NAT) to be +shared across multiple VPC tiers using the same public IP address. + +When conserve mode is ON: + - A single public IP can have rules targeting VMs in different VPC tiers + - FirewallManagerImpl skips the cross-network conflict check for that VPC + +When conserve mode is OFF (default before 4.23.0): + - Rules on a given public IP must all belong to the same VPC tier (network) + - Attempting to create a rule on a different tier than an existing rule raises + a NetworkRuleConflictException +""" + +from marvin.cloudstackException import CloudstackAPIException +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.codes import FAILED +from marvin.lib.base import ( + Account, + LoadBalancerRule, + NATRule, + Network, + NetworkOffering, + PublicIPAddress, + ServiceOffering, + VirtualMachine, + VPC, + VpcOffering, +) +from marvin.lib.common import ( + get_domain, + get_test_template, + get_zone, + list_publicIP +) +from marvin.lib.utils import cleanup_resources +from nose.plugins.attrib import attr +import logging + +class TestVPCConserveModeRules(cloudstackTestCase): + """Tests that conserve mode for VPC controls whether rules on the same public IP are allowed in multiple VPC tiers. + """ + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestVPCConserveModeRules, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.logger = logging.getLogger("TestVPCConserveModeRules") + cls._cleanup = [] + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + admin=True, + domainid=cls.domain.id) + cls._cleanup.append(cls.account) + + cls.template = get_test_template( + cls.apiclient, + cls.zone.id, + cls.hypervisor) + if cls.template == FAILED: + assert False, "get_test_template() failed to return template" + + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.services["service_offerings"]["tiny"] + ) + cls._cleanup.append(cls.service_offering) + + cls.services["vpc_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding' + cls.services["vpc_offering"]["conservemode"] = True + cls.vpc_offering_conserve_mode = VpcOffering.create( + cls.apiclient, + cls.services["vpc_offering"] + ) + cls.vpc_offering_conserve_mode.update(cls.apiclient, state="Enabled") + cls._cleanup.append(cls.vpc_offering_conserve_mode) + + cls.services["network_offering"]["supportedservices"] = 'Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat,NetworkACL,PortForwarding' + cls.services["network_offering"]["serviceProviderList"] = { + "Vpn": 'VpcVirtualRouter', + "Dhcp": 'VpcVirtualRouter', + "Dns": 'VpcVirtualRouter', + "SourceNat": 'VpcVirtualRouter', + "Lb": 'VpcVirtualRouter', + "UserData": 'VpcVirtualRouter', + "StaticNat": 'VpcVirtualRouter', + "NetworkACL": 'VpcVirtualRouter', + "PortForwarding": 'VpcVirtualRouter' + } + cls.network_offering = NetworkOffering.create( + cls.apiclient, + cls.services["network_offering"], + conservemode=True + ) + cls.network_offering.update(cls.apiclient, state="Enabled") + cls._cleanup.append(cls.network_offering) + + cls.services["vpc"]["cidr"] = "10.10.20.0/24" + + cls.vpc = VPC.create( + cls.apiclient, + cls.services["vpc"], + vpcofferingid=cls.vpc_offering_conserve_mode.id, + zoneid=cls.zone.id, + account=cls.account.name, + domainid=cls.account.domainid, + ) + cls._cleanup.append(cls.vpc) + + gateway_tier1 = "10.10.20.1" + netmask_tiers = "255.255.255.240" + + cls.services["network_offering"]["name"] = "tier1-" + cls.vpc.id + cls.services["network_offering"]["displayname"] = "tier1-" + cls.vpc.id + cls.tier1 = Network.create( + cls.apiclient, + services=cls.services["network_offering"], + accountid=cls.account.name, + domainid=cls.account.domainid, + networkofferingid=cls.network_offering.id, + zoneid=cls.zone.id, + vpcid=cls.vpc.id, + gateway=gateway_tier1, + netmask=netmask_tiers, + ) + cls._cleanup.append(cls.tier1) + + gateway_tier2 = "10.10.20.17" + cls.services["network_offering"]["name"] = "tier2-" + cls.vpc.id + cls.services["network_offering"]["displayname"] = "tier2-" + cls.vpc.id + cls.tier2 = Network.create( + cls.apiclient, + services=cls.services["network_offering"], + accountid=cls.account.name, + domainid=cls.account.domainid, + networkofferingid=cls.network_offering.id, + zoneid=cls.zone.id, + vpcid=cls.vpc.id, + gateway=gateway_tier2, + netmask=netmask_tiers, + ) + cls._cleanup.append(cls.tier2) + + cls.services["virtual_machine"]["displayname"] = "vm1" + cls.vpc.id + cls.vm1 = VirtualMachine.create( + cls.apiclient, + services=cls.services["virtual_machine"], + templateid=cls.template.id, + zoneid=cls.zone.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + networkids=[cls.tier1.id], + ) + cls.services["virtual_machine"]["displayname"] = "vm2" + cls.vpc.id + cls.vm2 = VirtualMachine.create( + cls.apiclient, + services=cls.services["virtual_machine"], + templateid=cls.template.id, + zoneid=cls.zone.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + networkids=[cls.tier2.id], + ) + cls._cleanup.append(cls.vm1) + cls._cleanup.append(cls.vm2) + + @classmethod + def tearDownClass(cls): + super(TestVPCConserveModeRules, cls).tearDownClass() + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + + def tearDown(self): + super(TestVPCConserveModeRules, self).tearDown() + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_01_vpc_conserve_mode_cross_tier_rules_allowed(self): + """With conserveMode=True, LB rule on VPC Tier 1 and Port Forwarding rule on VPC Tier 2 can + share the same public IP without a NetworkRuleConflictException. + """ + + public_ip = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + vpcid=self.vpc.id, + ) + + self.logger.debug( + "Creating LB rule on tier-1 (networkid=%s) using public IP %s", + self.tier1.id, + public_ip.ipaddress.ipaddress, + ) + lb_rule_tier1 = LoadBalancerRule.create( + self.apiclient, + self.services["lbrule"], + ipaddressid=public_ip.ipaddress.id, + accountid=self.account.name, + vpcid=self.vpc.id, + networkid=self.tier1.id, + domainid=self.account.domainid, + ) + self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-1 failed") + lb_rule_tier1.assign(self.apiclient, [self.vm1]) + + self.logger.debug( + "Creating Port Forwarding rule on tier-2 (networkid=%s) " + "using the same public IP %s – should succeed with conserve mode", + self.tier2.id, + public_ip.ipaddress.ipaddress, + ) + try: + nat_rule = NATRule.create( + self.apiclient, + self.vm2, + self.services["natrule"], + ipaddressid=public_ip.ipaddress.id, + vpcid=self.vpc.id, + networkid=self.tier2.id, + ) + self.assertIsNotNone( + nat_rule, + "Port Forwarding rule creation on tier-2 failed unexpectedly", + ) + except CloudstackAPIException as e: + self.fail( + "Expected cross-tier Port Forwarding rule to succeed with " + "conserveMode=True, but got exception: %s" % e + ) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_02_vpc_conserve_mode_reuse_source_nat_ip_address(self): + """With VPC conserve mode enabled, a NAT rule can be created on a VPC tier (conserve mode enabled) + with a source NAT IP address + """ + source_nat_ip_resp = list_publicIP( + self.apiclient, + vpcid=self.vpc.id, + listall=True, + issourcenat=True + ) + + source_nat_ip = source_nat_ip_resp[0] + + self.logger.debug( + "Creating Port Forwarding rule on tier-2 (networkid=%s) " + "using the source NAT public IP %s – should succeed with conserve mode", + self.tier1.id, + source_nat_ip.ipaddress, + ) + try: + nat_rule = NATRule.create( + self.apiclient, + self.vm2, + self.services["natrule"], + ipaddressid=source_nat_ip.id, + vpcid=self.vpc.id, + networkid=self.tier2.id, + ) + self.assertIsNotNone( + nat_rule, + "Port Forwarding rule creation on tier-2 failed unexpectedly", + ) + self.logger.debug( + "Creating LB rule on tier-1 (networkid=%s) " + "using the source NAT public IP %s – should succeed with conserve mode", + self.tier1.id, + source_nat_ip.ipaddress, + ) + lb_rule_tier1 = LoadBalancerRule.create( + self.apiclient, + self.services["lbrule"], + ipaddressid=source_nat_ip.id, + accountid=self.account.name, + vpcid=self.vpc.id, + networkid=self.tier2.id, + domainid=self.account.domainid, + ) + self.assertIsNotNone(lb_rule_tier1, "LB rule creation on tier-2 failed") + lb_rule_tier1.assign(self.apiclient, [self.vm2]) + except CloudstackAPIException as e: + self.fail( + "Expected multiple rules on VPC Source NAT IP to succeed with " + "conserveMode=True, but got exception: %s" % e + ) diff --git a/test/integration/smoke/test_vpc_firewall_rules.py b/test/integration/smoke/test_vpc_firewall_rules.py new file mode 100644 index 000000000000..6aae5f13fbcd --- /dev/null +++ b/test/integration/smoke/test_vpc_firewall_rules.py @@ -0,0 +1,187 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +"""Smoke tests for firewall rules on VPC public IPs.""" + +from nose.plugins.attrib import attr + +from marvin.cloudstackTestCase import cloudstackTestCase +from marvin.lib.base import Account, FireWallRule, Network, NetworkOffering, PublicIPAddress, VPC, VpcOffering +from marvin.lib.common import get_domain, get_zone, list_publicIP +from marvin.lib.utils import cleanup_resources, wait_until + + +class TestVpcFirewallRules(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.testClient = super(TestVpcFirewallRules, cls).getClsTestClient() + cls.apiclient = cls.testClient.getApiClient() + cls.services = cls.testClient.getParsedTestDataConfig() + cls.zone = get_zone(cls.apiclient, cls.testClient.getZoneForTests()) + cls.domain = get_domain(cls.apiclient) + cls._cleanup = [] + + cls.account = Account.create( + cls.apiclient, + cls.services["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + cls.services["vpc_offering"]["supportedservices"] = ( + "Vpn,Dhcp,Dns,SourceNat,Lb,UserData,StaticNat," + "NetworkACL,PortForwarding,Firewall" + ) + cls.services["vpc_offering"]["serviceProviderList"] = { + "Vpn": "VpcVirtualRouter", + "Dhcp": "VpcVirtualRouter", + "Dns": "VpcVirtualRouter", + "SourceNat": "VpcVirtualRouter", + "Lb": "VpcVirtualRouter", + "UserData": "VpcVirtualRouter", + "StaticNat": "VpcVirtualRouter", + "NetworkACL": "VpcVirtualRouter", + "PortForwarding": "VpcVirtualRouter", + "Firewall": "VpcVirtualRouter" + } + + cls.vpc_offering = VpcOffering.create( + cls.apiclient, + cls.services["vpc_offering"] + ) + cls.vpc_offering.update(cls.apiclient, state="Enabled") + cls._cleanup.append(cls.vpc_offering) + + network_offering = NetworkOffering.list( + cls.apiclient, + name="DefaultIsolatedNetworkOfferingForVpcNetworks" + ) + cls.assertTrue( + network_offering is not None and len(network_offering) > 0, + "No VPC tier network offering found" + ) + cls.network_offering = network_offering[0] + cls.services["vpc"]["cidr"] = "10.20.30.0/24" + cls.vpc = VPC.create( + cls.apiclient, + cls.services["vpc"], + vpcofferingid=cls.vpc_offering.id, + zoneid=cls.zone.id, + account=cls.account.name, + domainid=cls.account.domainid + ) + + cls.tier = Network.create( + cls.apiclient, + services={ + "name": "vpc-fw-tier", + "displaytext": "vpc-fw-tier" + }, + accountid=cls.account.name, + domainid=cls.account.domainid, + networkofferingid=cls.network_offering.id, + zoneid=cls.zone.id, + vpcid=cls.vpc.id, + gateway="10.20.30.1", + netmask="255.255.255.0" + ) + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup: %s" % e) + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.cleanup = [] + + def tearDown(self): + cleanup_resources(self.apiclient, self.cleanup) + + def _wait_for_firewall_rule(self, rule_id): + rules = FireWallRule.list(self.apiclient, id=rule_id, listall=True) + if rules and len(rules) == 1: + return True, rules[0] + return False, None + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_01_create_firewall_rule_on_vpc_public_ip(self): + """Verify firewall rule can be created and listed on a dedicated VPC public IP.""" + public_ip = PublicIPAddress.create( + self.apiclient, + zoneid=self.zone.id, + accountid=self.account.name, + domainid=self.account.domainid, + vpcid=self.vpc.id + ) + self.cleanup.append(public_ip) + + firewall_rule = FireWallRule.create( + self.apiclient, + ipaddressid=public_ip.ipaddress.id, + protocol="tcp", + cidrlist=["0.0.0.0/0"], + startport=19090, + endport=19090, + vpcid=self.vpc.id + ) + self.cleanup.insert(0, firewall_rule) + + result, listed_rule = wait_until(2, 10, self._wait_for_firewall_rule, firewall_rule.id) + self.assertTrue(result, "Firewall rule was not listed for the VPC public IP") + self.assertEqual(listed_rule.id, firewall_rule.id) + self.assertEqual(listed_rule.ipaddressid, public_ip.ipaddress.id) + self.assertEqual(listed_rule.vpcid, self.vpc.id) + self.assertEqual(listed_rule.protocol.lower(), "tcp") + self.assertEqual(int(listed_rule.startport), 19090) + self.assertEqual(int(listed_rule.endport), 19090) + + @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="false") + def test_02_create_firewall_rule_on_vpc_source_nat_ip(self): + """Verify firewall rule can be created and listed on the VPC source NAT IP.""" + source_nat_ips = list_publicIP( + self.apiclient, + vpcid=self.vpc.id, + listall=True, + issourcenat=True + ) + self.assertTrue(source_nat_ips is not None and len(source_nat_ips) > 0, + "No source NAT IP found for the VPC") + source_nat_ip = source_nat_ips[0] + + firewall_rule = FireWallRule.create( + self.apiclient, + ipaddressid=source_nat_ip.id, + protocol="tcp", + cidrlist=["0.0.0.0/0"], + startport=19443, + endport=19443, + vpcid=self.vpc.id + ) + self.cleanup.append(firewall_rule) + + result, listed_rule = wait_until(2, 10, self._wait_for_firewall_rule, firewall_rule.id) + self.assertTrue(result, "Firewall rule was not listed for the VPC source NAT IP") + self.assertEqual(listed_rule.id, firewall_rule.id) + self.assertEqual(listed_rule.ipaddressid, source_nat_ip.id) + self.assertEqual(listed_rule.vpcid, self.vpc.id) + self.assertEqual(listed_rule.protocol.lower(), "tcp") + self.assertEqual(int(listed_rule.startport), 19443) + self.assertEqual(int(listed_rule.endport), 19443) diff --git a/test/integration/smoke/test_webhook_lifecycle.py b/test/integration/smoke/test_webhook_lifecycle.py index 2d1f322be6b0..55947d71c809 100644 --- a/test/integration/smoke/test_webhook_lifecycle.py +++ b/test/integration/smoke/test_webhook_lifecycle.py @@ -283,7 +283,7 @@ def test_11_update_webhook(self): description=description, secretkey=secretkey, state=state - )['webhook'] + ) self.assertNotEqual( updated_webhook, None, diff --git a/test/integration/testpaths/testpath_storage_migration.py b/test/integration/testpaths/testpath_storage_migration.py index 5b821bcb5daf..c63cdfc8e22b 100644 --- a/test/integration/testpaths/testpath_storage_migration.py +++ b/test/integration/testpaths/testpath_storage_migration.py @@ -2809,7 +2809,7 @@ def test_01_migrate_live(self): disk=root_volume_cluster, disk_type="rootdiskdevice") - # Get Destnation Pool + # Get Destination Pool # Avoid storage Pool on which ROOT disk exists storagePools_to_avoid = [root_volume_cluster.storage] @@ -2840,7 +2840,7 @@ def test_01_migrate_live(self): disk=data_volume_1_cluster, disk_type="datadiskdevice_1") - # Get Destnation Pool + # Get Destination Pool # Avoid storage Pool allocated for ROOT disk, and Pool on which DATA # disk1 exists storagePools_to_avoid = [ @@ -3021,7 +3021,7 @@ def test_02_migration_live_different_pods(self): disk=root_volume_cluster, disk_type="rootdiskdevice") - # Get Destnation Pool + # Get Destination Pool # Avoid storage Pool on which ROOT disk exists storagePools_to_avoid = [root_volume_cluster.storage] @@ -3052,7 +3052,7 @@ def test_02_migration_live_different_pods(self): disk=data_volume_1_cluster, disk_type="datadiskdevice_1") - # Get Destnation Pool + # Get Destination Pool # Avoid storage Pool allocated for ROOT disk, and Pool on which DATA # disk1 exists diff --git a/test/metadata/delegatedAdmin/delegated_admin_cleanup.xml b/test/metadata/delegatedAdmin/delegated_admin_cleanup.xml index ac0f390ba905..4baf66283d6e 100644 --- a/test/metadata/delegatedAdmin/delegated_admin_cleanup.xml +++ b/test/metadata/delegatedAdmin/delegated_admin_cleanup.xml @@ -1,126 +1,126 @@ - - - - - - registerUserKeys - - - id - 2 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - listDomains - true - - - name - 0-1 - - - - - id - level1domain1id - - - - - - - listDomains - true - - - name - 0-2 - - - - - id - level1domain2id - - - - - - deleteUser - - - id - rootadmin1id - - - - - - deleteUser - - - id - rootadmin2id - - - - - - deleteDomain - - - id - level1domain1id - - - cleanup - true - - - - - - deleteDomain - - - id - level1domain2id - - - cleanup - true - - - - - - + + + + + + registerUserKeys + + + id + 2 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + listDomains + true + + + name + 0-1 + + + + + id + level1domain1id + + + + + + + listDomains + true + + + name + 0-2 + + + + + id + level1domain2id + + + + + + deleteUser + + + id + rootadmin1id + + + + + + deleteUser + + + id + rootadmin2id + + + + + + deleteDomain + + + id + level1domain1id + + + cleanup + true + + + + + + deleteDomain + + + id + level1domain2id + + + cleanup + true + + + + + + diff --git a/test/metadata/delegatedAdmin/delegated_admin_createusers.xml b/test/metadata/delegatedAdmin/delegated_admin_createusers.xml index 27ce77f6cf7a..46e0408db35b 100644 --- a/test/metadata/delegatedAdmin/delegated_admin_createusers.xml +++ b/test/metadata/delegatedAdmin/delegated_admin_createusers.xml @@ -1,312 +1,312 @@ - - - - - - - - createDomain - - - name - 0-1 - - - - - id - level1domain1id - - - - - - createDomain - - - name - 0-2 - - - - - id - level1domain2id - - - - - - createDomain - - - name - 0-1-1 - - - parentdomainid - level1domain1id - - - - - id - level2domain1id - - - - - - createDomain - - - name - 0-2-1 - - - parentdomainid - level1domain2id - - - - - id - level2domain2id - - - - - - - - createUser - - - username - - - password - 123 - - - firstname - 123 - - - lastname - 123 - - - email - 123@gmail.com - - - accounttype - 1 - - - - - id - rootadmin1id - - - account - rootadmin1account - - - - - - - createUser - - - username - - - password - 123 - - - firstname - 123 - - - lastname - 123 - - - email - 123@gmail.com - - - accounttype - 1 - - - - - id - rootadmin2id - - - account - rootadmin2account - - - - - - - - createUser - - - username - - - password - 123 - - - firstname - 123 - - - lastname - 123 - - - email - 123@gmail.com - - - accounttype - 2 - - - domainid - level1domain1id - - - - - id - domain11adminid - - - account - domain11adminaccount - - - - - - - - createUser - - - username - - - password - 123 - - - firstname - 123 - - - lastname - 123 - - - email - 123@gmail.com - - - accounttype - 2 - - - domainid - level1domain2id - - - - - id - domain12adminid - - - account - domain12adminaccount - - - - - - - createUser - - - username - - - password - 123 - - - firstname - 123 - - - lastname - 123 - - - email - 123@gmail.com - - - accounttype - 2 - - - domainid - level2domain1id - - - - - id - domain21adminid - - - account - domain21adminaccount - - - - - - + + + + + + + + createDomain + + + name + 0-1 + + + + + id + level1domain1id + + + + + + createDomain + + + name + 0-2 + + + + + id + level1domain2id + + + + + + createDomain + + + name + 0-1-1 + + + parentdomainid + level1domain1id + + + + + id + level2domain1id + + + + + + createDomain + + + name + 0-2-1 + + + parentdomainid + level1domain2id + + + + + id + level2domain2id + + + + + + + + createUser + + + username + + + password + 123 + + + firstname + 123 + + + lastname + 123 + + + email + 123@gmail.com + + + accounttype + 1 + + + + + id + rootadmin1id + + + account + rootadmin1account + + + + + + + createUser + + + username + + + password + 123 + + + firstname + 123 + + + lastname + 123 + + + email + 123@gmail.com + + + accounttype + 1 + + + + + id + rootadmin2id + + + account + rootadmin2account + + + + + + + + createUser + + + username + + + password + 123 + + + firstname + 123 + + + lastname + 123 + + + email + 123@gmail.com + + + accounttype + 2 + + + domainid + level1domain1id + + + + + id + domain11adminid + + + account + domain11adminaccount + + + + + + + + createUser + + + username + + + password + 123 + + + firstname + 123 + + + lastname + 123 + + + email + 123@gmail.com + + + accounttype + 2 + + + domainid + level1domain2id + + + + + id + domain12adminid + + + account + domain12adminaccount + + + + + + + createUser + + + username + + + password + 123 + + + firstname + 123 + + + lastname + 123 + + + email + 123@gmail.com + + + accounttype + 2 + + + domainid + level2domain1id + + + + + id + domain21adminid + + + account + domain21adminaccount + + + + + + diff --git a/test/metadata/delegatedAdmin/delegated_admin_verify_part1.xml b/test/metadata/delegatedAdmin/delegated_admin_verify_part1.xml index 1ce18187d524..9387c99c9993 100644 --- a/test/metadata/delegatedAdmin/delegated_admin_verify_part1.xml +++ b/test/metadata/delegatedAdmin/delegated_admin_verify_part1.xml @@ -1,420 +1,420 @@ - - - - - registerUserKeys - - - id - userid1 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - templateid - globaltemplateid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - - - id - vmid - - - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volumeid - - - - - - - - createTemplate - true - - - volumeid - volumeid - - - name - - - displaytext - newtext - - - osTypeId - 1 - - - - - id - privatetemplateid - - - - - - - registerTemplate - Register template - true - - - name - - - displaytext - - - url - globaltemplateurl - - - zoneid - globalzoneid - - - ostypeid - 1 - - - format - vhd - - - - - id - registeredtemplateid - - - - - - - - registerIso - true - Register iso - - - name - - - displaytext - - - url - globalisourl - - - ostypeid - 53 - - - zoneid - globalzoneid - - - - - id - isoid - - - - - - - createSnapshot - true - - - volumeid - volumeid - - - - - id - snapshotid - - - - - - createSnapshotPolicy - true - - - volumeid - volumeid - - - intervaltype - hourly - - - schedule - 10 - - - maxsnaps - 4 - - - timezone - Africa/Casablanca - - - - - id - snapshotpolicyid - - - - - - - - associateIpAddress - true - Associate ip address - - - zoneid - globalzoneid - - - - - ipaddress - ipaddress - - - - - - - - createPortForwardingService - Create port forwarding service - true - - - name - - - description - none - - - - - id - pfgroupid - - - - - - createPortForwardingService - Create port forwarding service - true - - - name - - - description - none - - - - - id - pfgroupid1 - - - - - - createPortForwardingServiceRule - Create port forwarding service rule - true - - - publicport - 22 - - - privateport - 22 - - - portforwardingserviceid - pfgroupid - - - - - id - pfruleid - - - - - - - - createLoadBalancerRule - Create load balancer rule - true - - - name - - - publicip - ipaddress - - - privateport - - - publicport - - - algorithm - roundrobin - - - - - id - lbid - - - - - - - createLoadBalancerRule - Create load balancer rule - true - - - name - - - publicip - ipaddress - - - privateport - - - publicport - - - algorithm - roundrobin - - - - - id - lbid1 - - - - - - assignToLoadBalancerRule - Assign to load balancer - true - - - id - lbid - - - virtualmachineid - vmid - - - - - + + + + + registerUserKeys + + + id + userid1 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + templateid + globaltemplateid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + + + id + vmid + + + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volumeid + + + + + + + + createTemplate + true + + + volumeid + volumeid + + + name + + + displaytext + newtext + + + osTypeId + 1 + + + + + id + privatetemplateid + + + + + + + registerTemplate + Register template + true + + + name + + + displaytext + + + url + globaltemplateurl + + + zoneid + globalzoneid + + + ostypeid + 1 + + + format + vhd + + + + + id + registeredtemplateid + + + + + + + + registerIso + true + Register iso + + + name + + + displaytext + + + url + globalisourl + + + ostypeid + 53 + + + zoneid + globalzoneid + + + + + id + isoid + + + + + + + createSnapshot + true + + + volumeid + volumeid + + + + + id + snapshotid + + + + + + createSnapshotPolicy + true + + + volumeid + volumeid + + + intervaltype + hourly + + + schedule + 10 + + + maxsnaps + 4 + + + timezone + Africa/Casablanca + + + + + id + snapshotpolicyid + + + + + + + + associateIpAddress + true + Associate ip address + + + zoneid + globalzoneid + + + + + ipaddress + ipaddress + + + + + + + + createPortForwardingService + Create port forwarding service + true + + + name + + + description + none + + + + + id + pfgroupid + + + + + + createPortForwardingService + Create port forwarding service + true + + + name + + + description + none + + + + + id + pfgroupid1 + + + + + + createPortForwardingServiceRule + Create port forwarding service rule + true + + + publicport + 22 + + + privateport + 22 + + + portforwardingserviceid + pfgroupid + + + + + id + pfruleid + + + + + + + + createLoadBalancerRule + Create load balancer rule + true + + + name + + + publicip + ipaddress + + + privateport + + + publicport + + + algorithm + roundrobin + + + + + id + lbid + + + + + + + createLoadBalancerRule + Create load balancer rule + true + + + name + + + publicip + ipaddress + + + privateport + + + publicport + + + algorithm + roundrobin + + + + + id + lbid1 + + + + + + assignToLoadBalancerRule + Assign to load balancer + true + + + id + lbid + + + virtualmachineid + vmid + + + + + diff --git a/test/metadata/delegatedAdmin/delegated_admin_verify_part2.xml b/test/metadata/delegatedAdmin/delegated_admin_verify_part2.xml index 9f8750544394..cad4305eb02f 100644 --- a/test/metadata/delegatedAdmin/delegated_admin_verify_part2.xml +++ b/test/metadata/delegatedAdmin/delegated_admin_verify_part2.xml @@ -1,952 +1,952 @@ - - - - - registerUserKeys - - - id - userid2 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - - - rebootVirtualMachine - Reboot virtual machine - true - - - id - vmid - - - - - - startVirtualMachine - Start virtual machine - true - - - id - vmid - - - - - - stopVirtualMachine - Stop virtual machine - true - - - id - vmid - - - - - - resetPasswordForVirtualMachine - Reset password for virtual machine - true - - - id - vmid - - - - - - changeServiceForVirtualMachine - Change service offering for virtual machine - true - - - id - vmid - - - serviceofferingid - 2 - - - - - - updateVirtualMachine - Update virtual machine - true - - - id - vmid - - - haenable - true - - - - - - listVirtualMachines - List virtual machines - true - - - id - vmid - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - templateid - globaltemplateid - - - serviceofferingid - 1 - - - diskofferingid - globaldiskofferingid - - - domainid - domainid1 - - - account - account1 - - - - - - - - listVolumes - List volumes - true - - - id - volumeid - - - - - - attachVolume - Attach volume - true - - - id - volumeid - - - virtualmachineid - vmid - - - - - - detachVolume - Detach volume - true - - - id - volumeid - - - - - - createVolume - Creating volume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - domainid - domainid1 - - - account - account1 - - - - - - - - listSnapshots - List snapshots - true - - - id - snapshotid - - - - - - createSnapshot - Creating snapshot - true - - - volumeid - volumeid - - - domainid - domainid1 - - - account - account1 - - - - - - listSnapshotPolicies - Listing snapshot policies - true - - - volumeid - volumeid - - - - - - createSnapshotPolicy - true - - - volumeid - volumeid - - - intervaltype - hourly - - - schedule - 10 - - - maxsnaps - 4 - - - timezone - Africa/Casablanca - - - domainid - domainid1 - - - account - account1 - - - - - - - updateTemplate - Update template - true - - - id - privatetemplateid - - - displaytext - hello - - - - - - updateTemplatePermissions - Update template permissions - true - - - id - privatetemplateid - - - ispublic - true - - - - - - copyTemplate - Copy template - true - - - id - privatetemplateid - - - sourcezoneid - globalzoneid - - - destzoneid - globalzoneid1 - - - - - - listTemplates - List templates - true - - - id - privatetemplateid - - - - - - listTemplatePermissions - List template permissions - true - - - id - privatetemplateid - - - - - - createTemplate - Create template - true - - - volumeid - volumeid - - - name - - - displaytext - newtext - - - osTypeId - 1 - - - domainid - domainid1 - - - account - account1 - - - - - - - - updateTemplate - Update template - true - - - id - registeredtemplateid - - - displaytext - hello - - - - - - updateTemplatePermissions - Update template permissions - true - - - id - registeredtemplateid - - - ispublic - true - - - - - - copyTemplate - Copy template - true - - - id - registeredtemplateid - - - sourcezoneid - globalzoneid - - - destzoneid - globalzoneid1 - - - - - - listTemplates - List templates - true - - - id - registeredtemplateid - - - - - - listTemplatePermissions - List template permissions - true - - - id - registeredtemplateid - - - - - - - - - updateIso - Update iso - true - - - id - isoid - - - displaytext - hello - - - - - - listIsos - List isos - true - - - id - isoid - - - - - - copyIso - Copy iso - true - - - id - isoid - - - sourcezoneid - globalzoneid - - - destzoneid - globalzoneid1 - - - - - - attachIso - true - Attaching iso - - - virtualmachineid - vmid - - - id - isoid - - - - - - detachIso - true - Detaching iso - - - virtualmachineid - vmid - - - - - - - listPublicIpAddresses - true - List public ip addresses - - - ipaddress - ipaddress - - - - - - associateIpAddress - true - Associate ip address - - - zoneid - globalzoneid - - - domainid - domainid1 - - - account - account1 - - - - - - - - listPortForwardingServices - List port forwarding services - true - - - id - pfgroupid - - - - - - listPortForwardingServiceRules - List port forwarding service rule - true - - - id - pfgroupid - - - - - - listPortForwardingServicesByVm - List port forwarding services by vm - true - - - virtualmachineid - vmid - - - - - - removePortForwardingService - true - Remove port forwarding service - - - id - pfgroupid - - - virtualmachineid - vmid - - - publicip - ipaddress - - - - - - assignPortForwardingService - true - Assigning port forwarding service - - - id - pfgroupid1 - - - virtualmachineid - vmid - - - publicip - ipaddress - - - - - - createPortForwardingService - Create port forwarding service - true - - - name - - - description - none - - - domainid - domainid1 - - - account - account1 - - - - - - createPortForwardingServiceRule - Create port forwarding service rule - true - - - publicport - 222 - - - privateport - 222 - - - portforwardingserviceid - pfgroupid - - - - - - deletePortForwardingServiceRule - Delete port forwarding service rule - true - - - id - pfruleid - - - - - - - deletePortForwardingService - true - Deleting port forwarding service - - - id - pfgroupid - - - - - - - - listLoadBalancerRules - List load balancer rules - true - - - id - lbid - - - - - - listLoadBalancerRuleInstances - List load balancer rule instances - true - - - id - lbid - - - - - - removeFromLoadBalancerRule - Remove from load balancer - true - - - id - lbid - - - virtualmachineid - vmid - - - - - - assignToLoadBalancerRule - Assign to load balancer - true - - - id - lbid1 - - - virtualmachineid - vmid - - - - - - deleteLoadBalancerRule - Delete load balancer rule - true - - - id - lbid - - - - - - createLoadBalancerRule - Create load balancer rule - true - - - name - - - publicip - ipaddress - - - privateport - - - publicport - - - algorithm - roundrobin - - - - - - - - disassociateIpAddress - true - Disassociate ip address - - - ipaddress - ipaddress - - - - - deleteSnapshot - Deleting snapshot - true - - - id - snapshotid - - - - - - deleteSnapshotPolicies - Deleting snapshot policy - true - - - id - snapshotpolicyid - - - - - - deleteTemplate - Delete template - true - - - id - privatetemplateid - - - - - - deleteTemplate - Delete template - true - - - id - registeredtemplateid - - - - - - deleteVolume - Deleting volume - true - - - id - volumeid - - - - - - deleteIso - Delete iso - true - - - id - isoid - - - - - - destroyVirtualMachine - Destroy virtual machine - true - - - id - vmid - - - - - + + + + + registerUserKeys + + + id + userid2 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + + + rebootVirtualMachine + Reboot virtual machine + true + + + id + vmid + + + + + + startVirtualMachine + Start virtual machine + true + + + id + vmid + + + + + + stopVirtualMachine + Stop virtual machine + true + + + id + vmid + + + + + + resetPasswordForVirtualMachine + Reset password for virtual machine + true + + + id + vmid + + + + + + changeServiceForVirtualMachine + Change service offering for virtual machine + true + + + id + vmid + + + serviceofferingid + 2 + + + + + + updateVirtualMachine + Update virtual machine + true + + + id + vmid + + + haenable + true + + + + + + listVirtualMachines + List virtual machines + true + + + id + vmid + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + templateid + globaltemplateid + + + serviceofferingid + 1 + + + diskofferingid + globaldiskofferingid + + + domainid + domainid1 + + + account + account1 + + + + + + + + listVolumes + List volumes + true + + + id + volumeid + + + + + + attachVolume + Attach volume + true + + + id + volumeid + + + virtualmachineid + vmid + + + + + + detachVolume + Detach volume + true + + + id + volumeid + + + + + + createVolume + Creating volume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + domainid + domainid1 + + + account + account1 + + + + + + + + listSnapshots + List snapshots + true + + + id + snapshotid + + + + + + createSnapshot + Creating snapshot + true + + + volumeid + volumeid + + + domainid + domainid1 + + + account + account1 + + + + + + listSnapshotPolicies + Listing snapshot policies + true + + + volumeid + volumeid + + + + + + createSnapshotPolicy + true + + + volumeid + volumeid + + + intervaltype + hourly + + + schedule + 10 + + + maxsnaps + 4 + + + timezone + Africa/Casablanca + + + domainid + domainid1 + + + account + account1 + + + + + + + updateTemplate + Update template + true + + + id + privatetemplateid + + + displaytext + hello + + + + + + updateTemplatePermissions + Update template permissions + true + + + id + privatetemplateid + + + ispublic + true + + + + + + copyTemplate + Copy template + true + + + id + privatetemplateid + + + sourcezoneid + globalzoneid + + + destzoneid + globalzoneid1 + + + + + + listTemplates + List templates + true + + + id + privatetemplateid + + + + + + listTemplatePermissions + List template permissions + true + + + id + privatetemplateid + + + + + + createTemplate + Create template + true + + + volumeid + volumeid + + + name + + + displaytext + newtext + + + osTypeId + 1 + + + domainid + domainid1 + + + account + account1 + + + + + + + + updateTemplate + Update template + true + + + id + registeredtemplateid + + + displaytext + hello + + + + + + updateTemplatePermissions + Update template permissions + true + + + id + registeredtemplateid + + + ispublic + true + + + + + + copyTemplate + Copy template + true + + + id + registeredtemplateid + + + sourcezoneid + globalzoneid + + + destzoneid + globalzoneid1 + + + + + + listTemplates + List templates + true + + + id + registeredtemplateid + + + + + + listTemplatePermissions + List template permissions + true + + + id + registeredtemplateid + + + + + + + + + updateIso + Update iso + true + + + id + isoid + + + displaytext + hello + + + + + + listIsos + List isos + true + + + id + isoid + + + + + + copyIso + Copy iso + true + + + id + isoid + + + sourcezoneid + globalzoneid + + + destzoneid + globalzoneid1 + + + + + + attachIso + true + Attaching iso + + + virtualmachineid + vmid + + + id + isoid + + + + + + detachIso + true + Detaching iso + + + virtualmachineid + vmid + + + + + + + listPublicIpAddresses + true + List public ip addresses + + + ipaddress + ipaddress + + + + + + associateIpAddress + true + Associate ip address + + + zoneid + globalzoneid + + + domainid + domainid1 + + + account + account1 + + + + + + + + listPortForwardingServices + List port forwarding services + true + + + id + pfgroupid + + + + + + listPortForwardingServiceRules + List port forwarding service rule + true + + + id + pfgroupid + + + + + + listPortForwardingServicesByVm + List port forwarding services by vm + true + + + virtualmachineid + vmid + + + + + + removePortForwardingService + true + Remove port forwarding service + + + id + pfgroupid + + + virtualmachineid + vmid + + + publicip + ipaddress + + + + + + assignPortForwardingService + true + Assigning port forwarding service + + + id + pfgroupid1 + + + virtualmachineid + vmid + + + publicip + ipaddress + + + + + + createPortForwardingService + Create port forwarding service + true + + + name + + + description + none + + + domainid + domainid1 + + + account + account1 + + + + + + createPortForwardingServiceRule + Create port forwarding service rule + true + + + publicport + 222 + + + privateport + 222 + + + portforwardingserviceid + pfgroupid + + + + + + deletePortForwardingServiceRule + Delete port forwarding service rule + true + + + id + pfruleid + + + + + + + deletePortForwardingService + true + Deleting port forwarding service + + + id + pfgroupid + + + + + + + + listLoadBalancerRules + List load balancer rules + true + + + id + lbid + + + + + + listLoadBalancerRuleInstances + List load balancer rule instances + true + + + id + lbid + + + + + + removeFromLoadBalancerRule + Remove from load balancer + true + + + id + lbid + + + virtualmachineid + vmid + + + + + + assignToLoadBalancerRule + Assign to load balancer + true + + + id + lbid1 + + + virtualmachineid + vmid + + + + + + deleteLoadBalancerRule + Delete load balancer rule + true + + + id + lbid + + + + + + createLoadBalancerRule + Create load balancer rule + true + + + name + + + publicip + ipaddress + + + privateport + + + publicport + + + algorithm + roundrobin + + + + + + + + disassociateIpAddress + true + Disassociate ip address + + + ipaddress + ipaddress + + + + + deleteSnapshot + Deleting snapshot + true + + + id + snapshotid + + + + + + deleteSnapshotPolicies + Deleting snapshot policy + true + + + id + snapshotpolicyid + + + + + + deleteTemplate + Delete template + true + + + id + privatetemplateid + + + + + + deleteTemplate + Delete template + true + + + id + registeredtemplateid + + + + + + deleteVolume + Deleting volume + true + + + id + volumeid + + + + + + deleteIso + Delete iso + true + + + id + isoid + + + + + + destroyVirtualMachine + Destroy virtual machine + true + + + id + vmid + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_domainlevel1_domainlevel2.xml b/test/metadata/delegatedAdmin/pickuser_domainlevel1_domainlevel2.xml index 6b66d9aad332..2ed6742f8b98 100644 --- a/test/metadata/delegatedAdmin/pickuser_domainlevel1_domainlevel2.xml +++ b/test/metadata/delegatedAdmin/pickuser_domainlevel1_domainlevel2.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - domain11adminid - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - domain21adminid - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + domain11adminid + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + domain21adminid + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_domainlevel1admin.xml b/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_domainlevel1admin.xml index f283affe292e..1b5697c459e2 100644 --- a/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_domainlevel1admin.xml +++ b/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_domainlevel1admin.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - domain11adminid - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - domain12adminid - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + domain11adminid + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + domain12adminid + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_rootadmin.xml b/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_rootadmin.xml index 29c4a5ac01e4..caf3bacaa940 100644 --- a/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_rootadmin.xml +++ b/test/metadata/delegatedAdmin/pickuser_domainlevel1admin_rootadmin.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - domain11adminid - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - rootadmin1id - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + domain11adminid + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + rootadmin1id + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_domainlevel2_child_domainlevel1.xml b/test/metadata/delegatedAdmin/pickuser_domainlevel2_child_domainlevel1.xml index 662fe0f25c16..97cd60f5653c 100644 --- a/test/metadata/delegatedAdmin/pickuser_domainlevel2_child_domainlevel1.xml +++ b/test/metadata/delegatedAdmin/pickuser_domainlevel2_child_domainlevel1.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - domain21adminid - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - domain11adminid - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + domain21adminid + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + domain11adminid + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_domainlevel2_nonchild_domainlevel1.xml b/test/metadata/delegatedAdmin/pickuser_domainlevel2_nonchild_domainlevel1.xml index 18a7b421343f..a45899eaee82 100644 --- a/test/metadata/delegatedAdmin/pickuser_domainlevel2_nonchild_domainlevel1.xml +++ b/test/metadata/delegatedAdmin/pickuser_domainlevel2_nonchild_domainlevel1.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - domain21adminid - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - domain12adminid - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + domain21adminid + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + domain12adminid + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_domainlevel2_rootadmin1.xml b/test/metadata/delegatedAdmin/pickuser_domainlevel2_rootadmin1.xml index c1b91351cbb4..9a83f624fc55 100644 --- a/test/metadata/delegatedAdmin/pickuser_domainlevel2_rootadmin1.xml +++ b/test/metadata/delegatedAdmin/pickuser_domainlevel2_rootadmin1.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - domain21adminid - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - rootadmin1id - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + domain21adminid + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + rootadmin1id + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_rootadmin1_rootadmin2.xml b/test/metadata/delegatedAdmin/pickuser_rootadmin1_rootadmin2.xml index aa85730890a7..63a0bede59b3 100644 --- a/test/metadata/delegatedAdmin/pickuser_rootadmin1_rootadmin2.xml +++ b/test/metadata/delegatedAdmin/pickuser_rootadmin1_rootadmin2.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - rootadmin1id - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - rootadmin2id - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + rootadmin1id + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + rootadmin2id + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/delegatedAdmin/pickuser_rootadmin_vs_domainlevel1admin.xml b/test/metadata/delegatedAdmin/pickuser_rootadmin_vs_domainlevel1admin.xml index 79da605f3638..f00c71eca22c 100644 --- a/test/metadata/delegatedAdmin/pickuser_rootadmin_vs_domainlevel1admin.xml +++ b/test/metadata/delegatedAdmin/pickuser_rootadmin_vs_domainlevel1admin.xml @@ -1,122 +1,122 @@ - - - - - - - - listUsers - - - id - rootadmin1id - - - - - id - userid1 - - - domainid - domainid1 - - - accounttype - accounttype1 - - - account - account1 - - - - - - listDomains - - - id - domainid1 - - - - - name - domainname1 - - - level - domainlevel1 - - - - - - - - listUsers - - - id - domain11adminid - - - - - id - userid2 - - - domainid - domainid2 - - - accounttype - accounttype2 - - - account - account2 - - - - - - listDomains - - - id - domainid2 - - - - - name - domainname2 - - - level - domainlevel2 - - - - - + + + + + + + + listUsers + + + id + rootadmin1id + + + + + id + userid1 + + + domainid + domainid1 + + + accounttype + accounttype1 + + + account + account1 + + + + + + listDomains + + + id + domainid1 + + + + + name + domainname1 + + + level + domainlevel1 + + + + + + + + listUsers + + + id + domain11adminid + + + + + id + userid2 + + + domainid + domainid2 + + + accounttype + accounttype2 + + + account + account2 + + + + + + listDomains + + + id + domainid2 + + + + + name + domainname2 + + + level + domainlevel2 + + + + + diff --git a/test/metadata/func/directnw_regression.xml b/test/metadata/func/directnw_regression.xml index 3cce8b1f869e..1e8f51c54945 100644 --- a/test/metadata/func/directnw_regression.xml +++ b/test/metadata/func/directnw_regression.xml @@ -1,1910 +1,1910 @@ - - - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - nimbus-account - - - accounttype - 1 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - admin - - - lastname - adminlastname - - - username - usernameparam - - - password - password - - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmavailable - Unlimited - - - vmlimit - Unlimited - - - vmtotal - 0 - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - - id - userid - - - username - usernameparam - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - - - - - - - - createUser - 3 [Create User Command] - - - account - nimbus-account - - - accounttype - 0 - - - username - secondusernameparam - - - password - password - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - - - - id - seconduserid - - - username - usernameparam - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - created - createtimestamp - - - state - enabled - - - - - - - - - listAccounts - 950 [List the Account of the User created in (3)] - - - name - accountname - - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - updateUser - 6 [Update the User created in (3)] - - - id - userid - - - username - newuser - - - password - password - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - listUsers - 5 [List the Updated User updated in (6)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - - - - - - - disableUser - 428 [Disable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - disabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - enableUser - 9 [Enable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - disableAccount - [Lock the Account] - - - account - accountname - - - domainid - 1 - - - lock - true - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - locked - - - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - 1 - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - disableAccount - [Lock the Account] - - - account - accountname - - - domainid - 1 - - - lock - false - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - disabled - - - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - 1 - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - createDomain - 10 [Create Domain] - - - name - domainname - - - - - - name - domainname - - - id - newdomainid - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - listDomains - 11 [List Domains] - - - name - domainname - - - - - - id - newdomainid - - - name - domainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - updateDomain - 952 [Update the Domain created in (10)] - - - id - newdomainid - - - name - newdomainname - - - - - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - - listDomainChildren - - List One of the Children of the Parent Domain - - - - id - 1 - - - - - domain - 0 - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - deleteDomain - 654 [Delete Domain created in (10)] - - - id - newdomainid - - - cleanup - true - - - - - - success - true - - - - - - - - - listTemplates - 27 [List Templates - List System Template] - - - templatefilter - self - - - id - 1 - - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - passwordenabled - debian50templatepasswordenabled - - - format - VHD - - - - isfeatured - false - - - crossZones - true - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - size - debian50templatesize - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - domain - ROOT - - - domainid - 1 - - - - - - - - listTemplates - - Verify All the Parameters of the Listed Template - - - - name - debian50templatename - - - templatefilter - self - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - isfeatured - false - - - passwordenabled - debian50templatepasswordenabled - - - crossZones - true - - - format - VHD - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - debian50templatesize - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - - - - - - - listServiceOfferings - 28 [List the Small Instance Service Offering] - - - id - globalserviceofferingid - - - - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - - - listServiceOfferings - 876 [Verify the First Service Offering] - - - name - smallofferingname - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - - - - listZones - 39 [List the Global Zone with id-2] - - - zone - 0 - - id - globalzoneid - - - name - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - internaldns1 - internaldns1 - - - vlan - - - guestcidraddress - - - networktype - Basic - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + nimbus-account + + + accounttype + 1 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + admin + + + lastname + adminlastname + + + username + usernameparam + + + password + password + + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmavailable + Unlimited + + + vmlimit + Unlimited + + + vmtotal + 0 + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + + id + userid + + + username + usernameparam + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + + + + + + + + createUser + 3 [Create User Command] + + + account + nimbus-account + + + accounttype + 0 + + + username + secondusernameparam + + + password + password + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + + + + id + seconduserid + + + username + usernameparam + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + created + createtimestamp + + + state + enabled + + + + + + + + + listAccounts + 950 [List the Account of the User created in (3)] + + + name + accountname + + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + updateUser + 6 [Update the User created in (3)] + + + id + userid + + + username + newuser + + + password + password + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + listUsers + 5 [List the Updated User updated in (6)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + + + + + + + disableUser + 428 [Disable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + disabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + enableUser + 9 [Enable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + disableAccount + [Lock the Account] + + + account + accountname + + + domainid + 1 + + + lock + true + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + locked + + + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + 1 + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + disableAccount + [Lock the Account] + + + account + accountname + + + domainid + 1 + + + lock + false + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + disabled + + + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + 1 + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + createDomain + 10 [Create Domain] + + + name + domainname + + + + + + name + domainname + + + id + newdomainid + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + listDomains + 11 [List Domains] + + + name + domainname + + + + + + id + newdomainid + + + name + domainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + updateDomain + 952 [Update the Domain created in (10)] + + + id + newdomainid + + + name + newdomainname + + + + + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + + listDomainChildren + - List One of the Children of the Parent Domain - + + + id + 1 + + + + + domain + 0 + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + deleteDomain + 654 [Delete Domain created in (10)] + + + id + newdomainid + + + cleanup + true + + + + + + success + true + + + + + + + + + listTemplates + 27 [List Templates - List System Template] + + + templatefilter + self + + + id + 1 + + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + passwordenabled + debian50templatepasswordenabled + + + format + VHD + + + + isfeatured + false + + + crossZones + true + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + size + debian50templatesize + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + domain + ROOT + + + domainid + 1 + + + + + + + + listTemplates + - Verify All the Parameters of the Listed Template - + + + name + debian50templatename + + + templatefilter + self + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + isfeatured + false + + + passwordenabled + debian50templatepasswordenabled + + + crossZones + true + + + format + VHD + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + debian50templatesize + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + + + + + + + listServiceOfferings + 28 [List the Small Instance Service Offering] + + + id + globalserviceofferingid + + + + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + + + listServiceOfferings + 876 [Verify the First Service Offering] + + + name + smallofferingname + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + + + + listZones + 39 [List the Global Zone with id-2] + + + zone + 0 + + id + globalzoneid + + + name + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + internaldns1 + internaldns1 + + + vlan + + + guestcidraddress + + + networktype + Basic + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/metadata/func/expunge.xml b/test/metadata/func/expunge.xml index d008dc3319f3..46f9109951a2 100644 --- a/test/metadata/func/expunge.xml +++ b/test/metadata/func/expunge.xml @@ -1,1207 +1,1207 @@ - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - volumesexpungeadmin - - - lastname - volumesexpungeadminlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - - id - userid - - - - - - - - registerUserKeys - Registering the user - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - - deployVirtualMachine - true - Deploy vm from regular centos template - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - VolumeExpungeTest-VM - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - name - vmname - - - - - - listVirtualMachines - List virtual machines - - - id - vmid - - - - - hostid - vm_hostid - - - - - - listHosts - List host where vm is running - - - id - vm_hostid - - - - - ipaddress - vmhost_ipaddress - - - - - - listRouters - List Routers - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - router_id - - - hostid - router_hostid - - - name - routername - - - - - - listHosts - List host where domR is running - - - id - router_hostid - - - - - ipaddress - routerhost_ipaddress - - - - - - listVolumes - List data disk vm volume - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - vm_datavolumeid - - - name - vm_datavolumename - - - - - - select path from volumes - true - Getting volume path for the vm DATADISK volume - - - id - vm_datavolumeid - - - - - path - vm_datavolumepath - - - - - - listVolumes - List root disk vm volume - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - vm_rootvolumeid - - - name - vm_rootvolumename - - - - - - select path from volumes - true - Getting volume path for the vm root volume - - - id - vm_rootvolumeid - - - - - path - vm_rootvolumepath - - - - - - select path from volumes - true - Getting volume path for the domR - - - instance_id - router_id - - - - - path - router_rootvolumepath - - - - - - listvdi.sh - - List VDI for vm DATADISK volume before vm is destroyed - - - h - vmhost_ipaddress - - - u - vm_datavolumepath - - - - - - listvdi.sh - - List VDI for vm ROOT volume before vm is destroyed - - - h - vmhost_ipaddress - - - u - vm_rootvolumepath - - - - - - listvdi.sh - - List VDI for router ROOT volume before domR is destroyed - - - h - routerhost_ipaddress - - - u - router_rootvolumepath - - - - - - - destroyVirtualMachine - Destroy virtual machine - - - id - vmid - - - - - - listConfigurations - Getting expunge interval value - - - name - expunge.interval - - - - - value - expunge.interval - - - - - - sleep.sh - - Sleep for expunge interval - - - s - expunge.interval - - - - - - - sleep.sh - - Sleep for another expunge interval - - - s - expunge.interval - - - - - - - listvdi.sh - true - - List VDI for vm DATADISK volume before vm is destroyed - - - h - vmhost_ipaddress - - - u - vm_datavolumepath - - - - - - listvdi.sh - true - - List VDI for vm ROOT volume before vm is destroyed - - - h - vmhost_ipaddress - - - u - vm_rootvolumepath - - - - - - - - createVolume - true - Creating a volume - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volumeid - - - - - - attachVolume - true - Attach the Volume to an Existing VM - - - id - volumeid - - - virtualmachineid - vmid - - - - - - detachVolume - true - Detach the Volume from the VM - - - id - volumeid - - - - - - createSnapshot - true - Create snapshot from detached volume - - - volumeid - volumeid - - - - - id - snapshotid - - - path - snapshotpath - - - - - - select path from volumes - true - Getting volume path for the detached volume - - - id - volumeid - - - - - path - detached_volumepath - - - - - - listvdi.sh - - List VDI for detached volume before the volume is deleted - - - h - vmhost_ipaddress - - - u - detached_volumepath - - - - - - deleteVolume - Delete detached volume - true - - - id - volumeid - - - - - - sleep.sh - - Sleep for expunge interval - - - s - expunge.interval - - - - - - - sleep.sh - - Sleep for another expunge interval - - - s - expunge.interval - - - - - - listvdi.sh - true - - List VDI for detached volume after the volume is deleted - - - h - vmhost_ipaddress - - - u - detached_volumepath - - - - - - - listvdi.sh - - List VDI for snapshot after it's created - - - h - vmhost_ipaddress - - - u - snapshotpath - - - - - - - - stopRouter - Stop the domR - - - id - router_id - - - - - - deleteUser - Deleting the user - - - id - userid - - - - - - listvdi.sh - true - - List VDI for domR after it's destroyed as a part of account cleanup - - - h - routerhost_ipaddress - - - u - router_rootvolumepath - - - - - - listvdi.sh - true - - List VDI for snapshot after corresponding account is deleted - - - h - vmhost_ipaddress - - - u - snapshotpath - - - - - - - - - - updateConfiguration - Updating storage.cleanup.interval - - - name - storage.cleanup.interval - - - value - 60 - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - - ms.sh - - Restarting management server - - - h - ms2 - - - o - restart - - - - - - - deployVirtualMachine - true - Deploying virtual machine from centos template - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - ExpungeTestVM-1 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - stopVirtualMachine - Stop virtual machine - - - id - vmid - - - - - - createTemplate - Create template from root volume - - - volumeid - rootvolumeid - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - privatetemplateid - - - - - - - deployVirtualMachine - Deploying virtual machine from the private template - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - privatetemplateid - - - account - admin - - - domainid - 1 - - - displayname - VMcreatedfrmTemplate - - - group - group1 - - - networkids - networkid - - - - - id - vmid1 - - - hostid - vm_hostid - - - - - - listHosts - List host where vm is running - - - id - vm_hostid - - - - - ipaddress - vmhost_ipaddress - - - - - - select install_path from template_spool_ref - true - Getting private template vdi from the db - - - template_id - privatetemplateid - - - - - install_path - template_uuid - - - - - - listvdi.sh - - List VDI for template on the xen host before vm is destroyed - - - h - vmhost_ipaddress - - - u - template_uuid - - - - - - destroyVirtualMachine - Destroy virtual machine created from the private template - - - id - vmid1 - - - - - - sleep.sh - - Sleep for storage cleanup interval - - - s - 240 - - - - - - listvdi.sh - true - - List VDI for template on the xen host after vm using template is destroyed - - - h - vmhost_ipaddress - - - u - template_uuid - - - - - - - - destroyVirtualMachine - Destroy virtual machine as a part of cleanup - - - id - vmid - - - - - - deleteTemplate - Delete private template - - - id - privatetemplateid - - - - - - updateConfiguration - Updating storage.cleanup.interval - - - name - storage.cleanup.interval - - - value - 60000 - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - ms.sh - - Restarting management server - - - h - ms2 - - - o - restart - - - - - - + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + volumesexpungeadmin + + + lastname + volumesexpungeadminlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + + id + userid + + + + + + + + registerUserKeys + Registering the user + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + + deployVirtualMachine + true + Deploy vm from regular centos template + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + VolumeExpungeTest-VM + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + name + vmname + + + + + + listVirtualMachines + List virtual machines + + + id + vmid + + + + + hostid + vm_hostid + + + + + + listHosts + List host where vm is running + + + id + vm_hostid + + + + + ipaddress + vmhost_ipaddress + + + + + + listRouters + List Routers + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + router_id + + + hostid + router_hostid + + + name + routername + + + + + + listHosts + List host where domR is running + + + id + router_hostid + + + + + ipaddress + routerhost_ipaddress + + + + + + listVolumes + List data disk vm volume + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + vm_datavolumeid + + + name + vm_datavolumename + + + + + + select path from volumes + true + Getting volume path for the vm DATADISK volume + + + id + vm_datavolumeid + + + + + path + vm_datavolumepath + + + + + + listVolumes + List root disk vm volume + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + vm_rootvolumeid + + + name + vm_rootvolumename + + + + + + select path from volumes + true + Getting volume path for the vm root volume + + + id + vm_rootvolumeid + + + + + path + vm_rootvolumepath + + + + + + select path from volumes + true + Getting volume path for the domR + + + instance_id + router_id + + + + + path + router_rootvolumepath + + + + + + listvdi.sh + + List VDI for vm DATADISK volume before vm is destroyed + + + h + vmhost_ipaddress + + + u + vm_datavolumepath + + + + + + listvdi.sh + + List VDI for vm ROOT volume before vm is destroyed + + + h + vmhost_ipaddress + + + u + vm_rootvolumepath + + + + + + listvdi.sh + + List VDI for router ROOT volume before domR is destroyed + + + h + routerhost_ipaddress + + + u + router_rootvolumepath + + + + + + + destroyVirtualMachine + Destroy virtual machine + + + id + vmid + + + + + + listConfigurations + Getting expunge interval value + + + name + expunge.interval + + + + + value + expunge.interval + + + + + + sleep.sh + + Sleep for expunge interval + + + s + expunge.interval + + + + + + + sleep.sh + + Sleep for another expunge interval + + + s + expunge.interval + + + + + + + listvdi.sh + true + + List VDI for vm DATADISK volume before vm is destroyed + + + h + vmhost_ipaddress + + + u + vm_datavolumepath + + + + + + listvdi.sh + true + + List VDI for vm ROOT volume before vm is destroyed + + + h + vmhost_ipaddress + + + u + vm_rootvolumepath + + + + + + + + createVolume + true + Creating a volume + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volumeid + + + + + + attachVolume + true + Attach the Volume to an Existing VM + + + id + volumeid + + + virtualmachineid + vmid + + + + + + detachVolume + true + Detach the Volume from the VM + + + id + volumeid + + + + + + createSnapshot + true + Create snapshot from detached volume + + + volumeid + volumeid + + + + + id + snapshotid + + + path + snapshotpath + + + + + + select path from volumes + true + Getting volume path for the detached volume + + + id + volumeid + + + + + path + detached_volumepath + + + + + + listvdi.sh + + List VDI for detached volume before the volume is deleted + + + h + vmhost_ipaddress + + + u + detached_volumepath + + + + + + deleteVolume + Delete detached volume + true + + + id + volumeid + + + + + + sleep.sh + + Sleep for expunge interval + + + s + expunge.interval + + + + + + + sleep.sh + + Sleep for another expunge interval + + + s + expunge.interval + + + + + + listvdi.sh + true + + List VDI for detached volume after the volume is deleted + + + h + vmhost_ipaddress + + + u + detached_volumepath + + + + + + + listvdi.sh + + List VDI for snapshot after it's created + + + h + vmhost_ipaddress + + + u + snapshotpath + + + + + + + + stopRouter + Stop the domR + + + id + router_id + + + + + + deleteUser + Deleting the user + + + id + userid + + + + + + listvdi.sh + true + + List VDI for domR after it's destroyed as a part of account cleanup + + + h + routerhost_ipaddress + + + u + router_rootvolumepath + + + + + + listvdi.sh + true + + List VDI for snapshot after corresponding account is deleted + + + h + vmhost_ipaddress + + + u + snapshotpath + + + + + + + + + + updateConfiguration + Updating storage.cleanup.interval + + + name + storage.cleanup.interval + + + value + 60 + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + + ms.sh + + Restarting management server + + + h + ms2 + + + o + restart + + + + + + + deployVirtualMachine + true + Deploying virtual machine from centos template + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + ExpungeTestVM-1 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + stopVirtualMachine + Stop virtual machine + + + id + vmid + + + + + + createTemplate + Create template from root volume + + + volumeid + rootvolumeid + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + privatetemplateid + + + + + + + deployVirtualMachine + Deploying virtual machine from the private template + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + privatetemplateid + + + account + admin + + + domainid + 1 + + + displayname + VMcreatedfrmTemplate + + + group + group1 + + + networkids + networkid + + + + + id + vmid1 + + + hostid + vm_hostid + + + + + + listHosts + List host where vm is running + + + id + vm_hostid + + + + + ipaddress + vmhost_ipaddress + + + + + + select install_path from template_spool_ref + true + Getting private template vdi from the db + + + template_id + privatetemplateid + + + + + install_path + template_uuid + + + + + + listvdi.sh + + List VDI for template on the xen host before vm is destroyed + + + h + vmhost_ipaddress + + + u + template_uuid + + + + + + destroyVirtualMachine + Destroy virtual machine created from the private template + + + id + vmid1 + + + + + + sleep.sh + + Sleep for storage cleanup interval + + + s + 240 + + + + + + listvdi.sh + true + + List VDI for template on the xen host after vm using template is destroyed + + + h + vmhost_ipaddress + + + u + template_uuid + + + + + + + + destroyVirtualMachine + Destroy virtual machine as a part of cleanup + + + id + vmid + + + + + + deleteTemplate + Delete private template + + + id + privatetemplateid + + + + + + updateConfiguration + Updating storage.cleanup.interval + + + name + storage.cleanup.interval + + + value + 60000 + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + ms.sh + + Restarting management server + + + h + ms2 + + + o + restart + + + + + + diff --git a/test/metadata/func/flatnetwork.xml b/test/metadata/func/flatnetwork.xml index 70648b312d3d..a07afab48434 100644 --- a/test/metadata/func/flatnetwork.xml +++ b/test/metadata/func/flatnetwork.xml @@ -1,533 +1,533 @@ - - - - createUser - Creating user - - - username - - - password - apiuser - - - firstname - apiuser - - - lastname - apiuser - - - email - apiuser@gmail.com - - - accounttype - 0 - - - - - id - userid - - - account - accountname - - - - - - registerUserKeys - Registering the user - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetworkGroup - true - Creating a network group - - - name - accountname - - - - - - authorizeNetworkGroupIngress - Authorizing network group ingress - - - networkgroupname - accountname - - - cidrlist - 192.168.1.143/32 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - deployVirtualMachine - true - Deploying vm - - - zoneid - globalzoneid - - - serviceofferingid - flatnetworkserviceofferingid - - - networkgrouplist - accountname - - - templateid - globaltemplateid - - - - - id - vmid - - - ipaddress - vmip - - - - - - deployVirtualMachine - true - Deploying vm - - - zoneid - globalzoneid - - - serviceofferingid - flatnetworkserviceofferingid - - - networkgrouplist - accountname - - - templateid - globaltemplateid - - - - - id - vmid1 - - - - - - createVolume - true - Creating a volume - - - name - - - zoneid - globalzoneid - - - diskofferingid - flatnetworkdiskofferingid - - - - - id - volumeid - - - - - - - attachVolume - true - Attaching the volume - - - id - volumeid - - - virtualmachineid - vmid - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - vmip - - - p - rs-ccb35ea5 - - - - - - sleep.sh - - Sleep for 1 min - - - s - 60 - - - - - - stopVirtualMachine - true - Stopping vm - - - id - vmid - - - - - - startVirtualMachine - true - Starting vm - - - id - vmid - - - - - - rebootVirtualMachine - true - Rebooting vm - - - id - vmid - - - - - - detachVolume - true - Detaching volume - - - id - volumeid - - - - - - attachVolume - true - Attaching the volume - - - id - volumeid - - - virtualmachineid - vmid1 - - - - - - - stopVirtualMachine - true - Stopping vm - - - id - vmid - - - - - - startVirtualMachine - true - Starting vm - - - id - vmid1 - - - - - - rebootVirtualMachine - true - Rebooting vm - - - id - vmid1 - - - - - - detachVolume - true - Detaching volume - - - id - volumeid - - - - - - - deleteVolume - true - Deleting volume - - - id - volumeid - - - - - - createVolume - true - Creating a new volume - - - name - - - zoneid - globalzoneid - - - diskofferingid - flatnetworkdiskofferingid - - - - - id - volumeid - - - - - - - attachVolume - true - Attaching the volume to the vm - - - id - volumeid - - - virtualmachineid - vmid - - - - - - - revokeNetworkGroupIngress - Revoking network group igress - - - networkgroupname - accountname - - - cidrlist - 192.168.1.143/32 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - - ssh.sh - - true - Ssh test for the vm - should fail as network group is revoked - - - h - vmip - - - p - password - - - - - - destroyVirtualMachine - true - Destroying virtual machine - - - id - vmid - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - - - deleteUser - Deleting the user - - - id - userid - - - - - + + + + createUser + Creating user + + + username + + + password + apiuser + + + firstname + apiuser + + + lastname + apiuser + + + email + apiuser@gmail.com + + + accounttype + 0 + + + + + id + userid + + + account + accountname + + + + + + registerUserKeys + Registering the user + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetworkGroup + true + Creating a network group + + + name + accountname + + + + + + authorizeNetworkGroupIngress + Authorizing network group ingress + + + networkgroupname + accountname + + + cidrlist + 192.168.1.143/32 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + deployVirtualMachine + true + Deploying vm + + + zoneid + globalzoneid + + + serviceofferingid + flatnetworkserviceofferingid + + + networkgrouplist + accountname + + + templateid + globaltemplateid + + + + + id + vmid + + + ipaddress + vmip + + + + + + deployVirtualMachine + true + Deploying vm + + + zoneid + globalzoneid + + + serviceofferingid + flatnetworkserviceofferingid + + + networkgrouplist + accountname + + + templateid + globaltemplateid + + + + + id + vmid1 + + + + + + createVolume + true + Creating a volume + + + name + + + zoneid + globalzoneid + + + diskofferingid + flatnetworkdiskofferingid + + + + + id + volumeid + + + + + + + attachVolume + true + Attaching the volume + + + id + volumeid + + + virtualmachineid + vmid + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + vmip + + + p + rs-ccb35ea5 + + + + + + sleep.sh + + Sleep for 1 min + + + s + 60 + + + + + + stopVirtualMachine + true + Stopping vm + + + id + vmid + + + + + + startVirtualMachine + true + Starting vm + + + id + vmid + + + + + + rebootVirtualMachine + true + Rebooting vm + + + id + vmid + + + + + + detachVolume + true + Detaching volume + + + id + volumeid + + + + + + attachVolume + true + Attaching the volume + + + id + volumeid + + + virtualmachineid + vmid1 + + + + + + + stopVirtualMachine + true + Stopping vm + + + id + vmid + + + + + + startVirtualMachine + true + Starting vm + + + id + vmid1 + + + + + + rebootVirtualMachine + true + Rebooting vm + + + id + vmid1 + + + + + + detachVolume + true + Detaching volume + + + id + volumeid + + + + + + + deleteVolume + true + Deleting volume + + + id + volumeid + + + + + + createVolume + true + Creating a new volume + + + name + + + zoneid + globalzoneid + + + diskofferingid + flatnetworkdiskofferingid + + + + + id + volumeid + + + + + + + attachVolume + true + Attaching the volume to the vm + + + id + volumeid + + + virtualmachineid + vmid + + + + + + + revokeNetworkGroupIngress + Revoking network group igress + + + networkgroupname + accountname + + + cidrlist + 192.168.1.143/32 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + + ssh.sh + + true + Ssh test for the vm - should fail as network group is revoked + + + h + vmip + + + p + password + + + + + + destroyVirtualMachine + true + Destroying virtual machine + + + id + vmid + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + + + deleteUser + Deleting the user + + + id + userid + + + + + diff --git a/test/metadata/func/ha.xml b/test/metadata/func/ha.xml index 32ad2c22a512..332ee0748541 100644 --- a/test/metadata/func/ha.xml +++ b/test/metadata/func/ha.xml @@ -1,367 +1,367 @@ - - - - - deployVirtualMachine - Deploy ha disabled vm - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - HADisabledVM - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - name - vmname - - - hostid - hostid - - - - - - deployVirtualMachine - Deploy ha enabled vm - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - HAEnabledVM - - - group - group2 - - - networkids - globalnetworkid - - - - - id - vmid_ha - - - name - vmname_ha - - - hostid - hostid_ha - - - - - - - updateVirtualMachine - Update virtual machine - - - id - vmid_ha - - - haenable - true - - - - - - - listHosts - List host with ha disabled machine - - - id - hostid - - - - - ipaddress - ipaddress - - - - - - listHosts - List host with ha enabled machine - - - id - hostid_ha - - - - - ipaddress - ipaddress_ha - - - - - - - - select instance_name from vm_instance - true - Getting internal instance name for the vm with ha disabled - - - id - vmid - - - - - instance_name - instance_name - - - - - - select instance_name from vm_instance - true - Getting internal instance name for the vm with ha enabled - - - id - vmid_ha - - - - - instance_name - instance_name_ha - - - - - - - killvm.sh - - Kill ha disabled vm on the backend - - - h - ipaddress - - - n - instance_name - - - - - - killvm.sh - - Kill ha enabled vm on the backend - - - h - ipaddress_ha - - - n - instance_name_ha - - - - - - - sleep.sh - - Sleep for some time - - - s - 300 - - - - - - - listVirtualMachines - List virtual machine with ha disabled - - - id - vmid - - - - - state - Stopped - - - - - - listVirtualMachines - List virtual machine with ha enabled - - - id - vmid_ha - - - - - state - Running - - - hostid - hostid_ha - - - - - - listHosts - List host with ha enabled machine - - - id - hostid_ha - - - - - ipaddress - ipaddress_ha - - - - - - listvm.sh - - List ha enabled machine - - - h - ipaddress_ha - - - n - instance_name_ha - - - - - - - - - destroyVirtualMachine - Destroy ha disabled vm as a part of cleanup - - - id - vmid - - - - - - destroyVirtualMachine - Destroy ha enabled vm as a part of cleanup - - - id - vmid_ha - - - - - + + + + + deployVirtualMachine + Deploy ha disabled vm + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + HADisabledVM + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + name + vmname + + + hostid + hostid + + + + + + deployVirtualMachine + Deploy ha enabled vm + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + HAEnabledVM + + + group + group2 + + + networkids + globalnetworkid + + + + + id + vmid_ha + + + name + vmname_ha + + + hostid + hostid_ha + + + + + + + updateVirtualMachine + Update virtual machine + + + id + vmid_ha + + + haenable + true + + + + + + + listHosts + List host with ha disabled machine + + + id + hostid + + + + + ipaddress + ipaddress + + + + + + listHosts + List host with ha enabled machine + + + id + hostid_ha + + + + + ipaddress + ipaddress_ha + + + + + + + + select instance_name from vm_instance + true + Getting internal instance name for the vm with ha disabled + + + id + vmid + + + + + instance_name + instance_name + + + + + + select instance_name from vm_instance + true + Getting internal instance name for the vm with ha enabled + + + id + vmid_ha + + + + + instance_name + instance_name_ha + + + + + + + killvm.sh + + Kill ha disabled vm on the backend + + + h + ipaddress + + + n + instance_name + + + + + + killvm.sh + + Kill ha enabled vm on the backend + + + h + ipaddress_ha + + + n + instance_name_ha + + + + + + + sleep.sh + + Sleep for some time + + + s + 300 + + + + + + + listVirtualMachines + List virtual machine with ha disabled + + + id + vmid + + + + + state + Stopped + + + + + + listVirtualMachines + List virtual machine with ha enabled + + + id + vmid_ha + + + + + state + Running + + + hostid + hostid_ha + + + + + + listHosts + List host with ha enabled machine + + + id + hostid_ha + + + + + ipaddress + ipaddress_ha + + + + + + listvm.sh + + List ha enabled machine + + + h + ipaddress_ha + + + n + instance_name_ha + + + + + + + + + destroyVirtualMachine + Destroy ha disabled vm as a part of cleanup + + + id + vmid + + + + + + destroyVirtualMachine + Destroy ha enabled vm as a part of cleanup + + + id + vmid_ha + + + + + diff --git a/test/metadata/func/iso.xml b/test/metadata/func/iso.xml index aa6d361e96c0..d40585f47cfc 100644 --- a/test/metadata/func/iso.xml +++ b/test/metadata/func/iso.xml @@ -1,907 +1,907 @@ - - - - - - registerUserKeys - - - id - 2 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - ISOTest-VM-1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - - - - attachIso - true - 869-1 - - - virtualmachineid - vmid - - - id - globalisoid - - - - - - listVirtualMachines - 869-2 - - - id - vmid - - - - - isoid - globalisoid - - - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - admin - - - lastname - adminlastname - - - username - usernamevar - - - password - password - - - - - id - accountid - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - id - userid1 - - - - - - - registerUserKeys - - - id - userid1 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - attachIso - true - 870 - true - - - virtualmachineid - vmid - - - id - globalisoid - - - - - - - - registerUserKeys - - - id - 2 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - destroyVirtualMachine - - - id - vmid - - - - - - attachIso - 871 - true - - - virtualmachineid - vmid - - - id - globalisoid - - - - - - - - attachIso - 872 - true - - - virtualmachineid - 600000 - - - id - globalisoid - - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - ISOTest-VM-2 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - - - - attachIso - true - - - virtualmachineid - vmid - - - id - globalisoid - - - - - - rebootVirtualMachine - true - 1073-1 - - - id - vmid - - - - - - listVirtualMachines - 1073-2 - - - id - vmid - - - - - isoid - globalisoid - - - - - - - stopVirtualMachine - true - 1074-1 - - - id - vmid - - - - - - listVirtualMachines - 1074-2 - - - id - vmid - - - - - isoid - globalisoid - - - - - - startVirtualMachine - true - 1074-3 - - - id - vmid - - - - - - listVirtualMachines - 1074-4 - - - id - vmid - - - - - isoid - globalisoid - - - - - - - - listRouters - 35 - - - zoneid - globalzoneid - - - account - admin - - - domainid - 1 - - - - router - 0 - - id - routerid - - - - - - attachIso - 1075 - true - - - virtualmachineid - routerid - - - id - globalisoid - - - - - - - detachIso - true - 1076 - - - virtualmachineid - vmid - - - - - - - - attachIso - true - - - virtualmachineid - vmid - - - id - globalisoid - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - detachIso - true - 1077 - - - virtualmachineid - vmid - - - - - - - - destroyVirtualMachine - Destroying vm as a part of cleanup - - - id - vmid - - - - - - deployVirtualMachine - true - 1078 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globalisoid - - - account - admin - - - domainid - 1 - - - displayname - ISOTest-VM-3 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - isoid - globalisoid - - - - - - - destroyVirtualMachine - Destroying vm as a part of cleanup - - - id - vmid - - - - - - deployVirtualMachine - true - 1079-1 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globalisoid - - - account - admin - - - domainid - 1 - - - displayname - ISOTest-VM-4 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - isoid - globalisoid - - - - - - detachIso - true - 1079-2 - - - virtualmachineid - vmid - - - - - - rebootVirtualMachine - true - 1079-3 - - - id - vmid - - - - - - listVirtualMachines - 1079-4 - - - id - vmid - - - - - templateid - globalisoid - - - - - - - - - destroyVirtualMachine - Destroying vm as a part of cleanup - - - id - vmid - - - - - deployVirtualMachine - true - 1080-1 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globalisoid - - - account - admin - - - domainid - 1 - - - displayname - ISOTest-VM-5 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - isoid - globalisoid - - - - - - detachIso - true - 1080-2 - - - virtualmachineid - vmid - - - - - - stopVirtualMachine - true - 1080-3 - - - id - vmid - - - - - - startVirtualMachine - true - 1080-4 - - - id - vmid - - - - - - listVirtualMachines - 1080-4 - - - id - vmid - - - - - templateid - globalisoid - - - - - - - - deleteUser - Deleting user as a part of cleanup - - - id - userid1 - - - - - - destroyVirtualMachine - Destroying vm as a part of cleanup - - - id - vmid - - - - - + + + + + + registerUserKeys + + + id + 2 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + ISOTest-VM-1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + + + + attachIso + true + 869-1 + + + virtualmachineid + vmid + + + id + globalisoid + + + + + + listVirtualMachines + 869-2 + + + id + vmid + + + + + isoid + globalisoid + + + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + admin + + + lastname + adminlastname + + + username + usernamevar + + + password + password + + + + + id + accountid + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + id + userid1 + + + + + + + registerUserKeys + + + id + userid1 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + attachIso + true + 870 + true + + + virtualmachineid + vmid + + + id + globalisoid + + + + + + + + registerUserKeys + + + id + 2 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + destroyVirtualMachine + + + id + vmid + + + + + + attachIso + 871 + true + + + virtualmachineid + vmid + + + id + globalisoid + + + + + + + + attachIso + 872 + true + + + virtualmachineid + 600000 + + + id + globalisoid + + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + ISOTest-VM-2 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + + + + attachIso + true + + + virtualmachineid + vmid + + + id + globalisoid + + + + + + rebootVirtualMachine + true + 1073-1 + + + id + vmid + + + + + + listVirtualMachines + 1073-2 + + + id + vmid + + + + + isoid + globalisoid + + + + + + + stopVirtualMachine + true + 1074-1 + + + id + vmid + + + + + + listVirtualMachines + 1074-2 + + + id + vmid + + + + + isoid + globalisoid + + + + + + startVirtualMachine + true + 1074-3 + + + id + vmid + + + + + + listVirtualMachines + 1074-4 + + + id + vmid + + + + + isoid + globalisoid + + + + + + + + listRouters + 35 + + + zoneid + globalzoneid + + + account + admin + + + domainid + 1 + + + + router + 0 + + id + routerid + + + + + + attachIso + 1075 + true + + + virtualmachineid + routerid + + + id + globalisoid + + + + + + + detachIso + true + 1076 + + + virtualmachineid + vmid + + + + + + + + attachIso + true + + + virtualmachineid + vmid + + + id + globalisoid + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + detachIso + true + 1077 + + + virtualmachineid + vmid + + + + + + + + destroyVirtualMachine + Destroying vm as a part of cleanup + + + id + vmid + + + + + + deployVirtualMachine + true + 1078 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globalisoid + + + account + admin + + + domainid + 1 + + + displayname + ISOTest-VM-3 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + isoid + globalisoid + + + + + + + destroyVirtualMachine + Destroying vm as a part of cleanup + + + id + vmid + + + + + + deployVirtualMachine + true + 1079-1 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globalisoid + + + account + admin + + + domainid + 1 + + + displayname + ISOTest-VM-4 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + isoid + globalisoid + + + + + + detachIso + true + 1079-2 + + + virtualmachineid + vmid + + + + + + rebootVirtualMachine + true + 1079-3 + + + id + vmid + + + + + + listVirtualMachines + 1079-4 + + + id + vmid + + + + + templateid + globalisoid + + + + + + + + + destroyVirtualMachine + Destroying vm as a part of cleanup + + + id + vmid + + + + + deployVirtualMachine + true + 1080-1 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globalisoid + + + account + admin + + + domainid + 1 + + + displayname + ISOTest-VM-5 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + isoid + globalisoid + + + + + + detachIso + true + 1080-2 + + + virtualmachineid + vmid + + + + + + stopVirtualMachine + true + 1080-3 + + + id + vmid + + + + + + startVirtualMachine + true + 1080-4 + + + id + vmid + + + + + + listVirtualMachines + 1080-4 + + + id + vmid + + + + + templateid + globalisoid + + + + + + + + deleteUser + Deleting user as a part of cleanup + + + id + userid1 + + + + + + destroyVirtualMachine + Destroying vm as a part of cleanup + + + id + vmid + + + + + diff --git a/test/metadata/func/loadbalancers.xml b/test/metadata/func/loadbalancers.xml index 87f2a0f98998..4ec01e13ede8 100644 --- a/test/metadata/func/loadbalancers.xml +++ b/test/metadata/func/loadbalancers.xml @@ -1,2036 +1,2036 @@ - - - - - - deployVirtualMachine - [Deploy Virtual Machine in the admin account] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - LoadBalancer-VM-1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - - - - associateIpAddress - [Acquire a non-Source NAT IP Address for the Admin Account] - - - zoneid - globalzoneid - - - account - admin - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip - - - - - - createLoadBalancerRule - [Create Load Balancing Rule -ve test (Private Port 0)] - true - 853 - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 0 - - - publicport - 986 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - [Create Load Balancing Rule -ve test (Private Port 65536)] - true - 853 - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 65536 - - - publicport - 986 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - [Create Load Balancing Rule -ve test (Private Port 65536)] - true - 853 - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 1 - - - publicport - 0 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - [Create Load Balancing Rule -ve test (Public Port 65536)] - true - 853 - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 65535 - - - publicport - 65536 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - - createLoadBalancerRule - 919 [Create Load Balancing Rule - 1(Set of LB Rules for Algorithm Validation begins)] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 9191 - - - publicport - 9191 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - 919-1 [Create Load Balancing Rule - 2] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 9192 - - - publicport - 9192 - - - algorithm - leastconn - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - 919-2 [Create Load Balancing Rule - 3] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 9193 - - - publicport - 9193 - - - algorithm - source - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - true - 919-3 [Create Load Balancing Rule - 4 (Set of LB Rules for Algorithm Validation ends)] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 9194 - - - publicport - 9194 - - - algorithm - roundrobinblahblah - - - account - admin - - - domainid - 1 - - - - - - - - - createLoadBalancerRule - 920-1 [Create Load Balancer Rule] - - - name - lbname - - - publicip - nonsourcenatpublicip - - - privateport - 9201 - - - publicport - 9201 - - - algorithm - source - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - 920-2 [Create a Load Balancer Rule with the Same Name] - - - name - lbname - - - publicip - nonsourcenatpublicip - - - privateport - 9202 - - - publicport - 9202 - - - algorithm - source - - - account - admin - - - domainid - 1 - - - - - - - - createLoadBalancerRule - 921-1 [Create Load Balancer Rule in one account] - - - name - lbname - - - publicip - nonsourcenatpublicip - - - privateport - 921 - - - publicport - 921 - - - algorithm - source - - - account - admin - - - domainid - 1 - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - apiuser@gmail.com - - - firstname - apiuser - - - lastname - apiuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - listUsers - [List the Created User in that account] - - - account - accountvar - - - - - id - userid - - - - - - - registerUserKeys - [Register User Keys] - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar - - - domainid - 1 - - - name - loadbalancersregressionnetwork - - - displaytext - loadbalancersregressionnetwork - - - - - id - networkid - - - - - - deployVirtualMachine - [Deploy Virtual Machine in that Account] - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - displayname - namefordisplay - - - group - group1 - - - account - accountvar - - - domainid - 1 - - - networkids - networkid - - - - - id - vmidtemp - - - - - - associateIpAddress - [Acquire a Non-Source NAT IP Address] - true - - - zoneid - globalzoneid - - - account - accountvar - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip1 - - - - - - createLoadBalancerRule - [Creating a LB Rule with the same name of a rule in another account] - true - 921-2 - - - name - lbname - - - publicip - nonsourcenatpublicip1 - - - privateport - 9211 - - - publicport - 9211 - - - algorithm - source - - - account - accountvar - - - domainid - 1 - - - - - - - - createLoadBalancerRule - 922 [Create Load Balancer Rule with Non-existing ip address -ve case] - true - - - name - - - publicip - 192.168.0.5 - - - privateport - 922 - - - publicport - 922 - - - algorithm - source - - - domainid - 1 - - - account - admin - - - - - - - - createLoadBalancerRule - true - 923 [Create Load Balancer Rule with Ip Address belonging to different user -ve case] - true - - - name - - - publicip - nonsourcenatpublicip1 - - - privateport - 923 - - - publicport - 923 - - - algorithm - source - - - domainid - 1 - - - account - admin - - - - - - - associateIpAddress - [Acquire a non-Source NAT Ip address] - - - zoneid - globalzoneid - - - account - admin - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip - - - - - - createLoadBalancerRule - 924 [Create Load Balancer Rule from non-Source NAT IP Address] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 924 - - - publicport - 924 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - - deleteLoadBalancerRule - [Try to a delete a Non-Existing Load Balancer Rule] - true - 925 - - - id - 10000 - - - - - - - deleteAccount - [Delete account] - - - id - accountid - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - apiuser@gmail.com - - - firstname - apiuser - - - lastname - apiuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - - listUsers - [List the Created User] - - - account - accountvar - - - - - id - userid - - - - - - - registerUserKeys - [Register User Keys] - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar - - - domainid - 1 - - - name - loadbalancersregressionnetwork - - - displaytext - loadbalancersregressionnetwork - - - - - id - networkid - - - - - - deployVirtualMachine - [Deploy Virtual Machine in the account created] - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - displayname - namefordisplay - - - group - group1 - - - account - accountvar - - - networkids - networkid - - - - - - associateIpAddress - [Acquire a non-Source NAT IP Address for that account] - true - - - zoneid - globalzoneid - - - account - accountvar - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip1 - - - - - - createLoadBalancerRule - [Create Load Balancer Rule with the acquired Ip address] - true - - - name - loadb9261 - - - publicip - nonsourcenatpublicip1 - - - privateport - 9261 - - - publicport - 9261 - - - algorithm - source - - - account - accountvar - - - domainid - 1 - - - - - id - lbid1 - - - - - - createLoadBalancerRule - [Create a Second Load Balancer Rule with the Acquired Ip Address] - true - - - name - loadb9262 - - - publicip - nonsourcenatpublicip1 - - - privateport - 9262 - - - publicport - 9262 - - - algorithm - source - - - account - accountvar - - - domainid - 1 - - - - - id - lbid2 - - - - - - createLoadBalancerRule - [Create a Third Load Balancer Rule with the Acquired Ip Address] - true - - - name - loadb9263 - - - publicip - nonsourcenatpublicip1 - - - privateport - 9263 - - - publicport - 9263 - - - algorithm - source - - - account - accountvar - - - domainid - 1 - - - - - id - lbid3 - - - - - - deleteLoadBalancerRule - 926 [Admin allowed to Delete Load Balancer Rule of another account via 8096 port] - - - id - lbid1 - - - - - - - - deleteAccount - [Delete Account as a part of Clean Up] - - - id - accountid - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - apiuser@gmail.com - - - firstname - apiuser - - - lastname - apiuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - - listUsers - [List the Created User of the Account] - - - account - accountvar - - - - - id - userid - - - - - - - registerUserKeys - [Register User Keys] - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - deleteLoadBalancerRule - [Admin allowed to Delete Load Balancer Rule of another account via 8080 port] - true - 927 - - - id - lbid2 - - - - - - - - deleteAccount - [Delete Account as part of Clean Up] - - - id - accountid - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - apiuser@gmail.com - - - firstname - apiuser - - - lastname - apiuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - - listUsers - [List the Created User of the account] - - - account - accountvar - - - - - id - userid - - - - - - - registerUserKeys - [Register User Keys] - - - id - userid - - - domainid - 1 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar - - - domainid - 1 - - - name - loadbalancers-regressionnetwork - - - displaytext - loadbalancers-defaultregressionnetwork - - - - - id - networkid - - - - - - deleteLoadBalancerRule - true - 928 [Regular User can't Delete LBrule belonging to Another User] - true - - - id - lbid3 - - - - - - - deployVirtualMachine - [Deploy a Virtual Machine-1 to check multiple VMs - LB assignment] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - LoadBalancers-VM-1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid1 - - - - - - deployVirtualMachine - [Deploy a Virtual Machine-2 to check multiple VMs - LB assignment] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - LoadBalancers-VM-2 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid2 - - - - - - associateIpAddress - [Acquire a non-Source NAT IP address] - - - zoneid - globalzoneid - - - account - admin - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip - - - - - - createLoadBalancerRule - [Create a Load Balancer Rule] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 931 - - - publicport - 931 - - - algorithm - source - - - domainid - 1 - - - account - admin - - - - - id - lbid - - - - - - assignToLoadBalancerRule - 939 [Assign the First VM to the Load Balancing Rule] - - - id - lbid - - - virtualmachineids - vmid1 - - - - - - assignToLoadBalancerRule - 939 [Assign the Second VM to the Load Balancing Rule] - - - id - lbid - - - virtualmachineids - vmid2 - - - - - - - createLoadBalancerRule - 978 [Create Load Balancing Rule (to test the creation of 2 LB Rules with the same settings )] - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 978 - - - publicport - 978 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - createLoadBalancerRule - 978 [Creation of Second LoadBalancer Rule should Fail] - true - - - name - - - publicip - nonsourcenatpublicip - - - privateport - 978 - - - publicport - 978 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - - listRouters - [Obtain the source nat IP Address - List Routers] - - - zoneid - globalzoneid - - - account - admin - - - domainid - 1 - - - - router - 0 - - publicip - sourcenatpublicip - - - - - - createLoadBalancerRule - 1017 [Create Load Balancer Rule with Source NAT IP Address] - - - name - - - publicip - sourcenatpublicip - - - privateport - 1017 - - - publicport - 1017 - - - algorithm - roundrobin - - - account - admin - - - domainid - 1 - - - - - - - - destroyVirtualMachine - [Destroy VM as a Part of Cleanup] - - - id - vmid1 - - - - - - destroyVirtualMachine - [Destroy vm as a part of cleanup] - - - id - vmid2 - - - - - - destroyVirtualMachine - [Destroy vm as a part of cleanup] - - - id - vmid - - - - - - deleteAccount - [Deleting Account as a part of cleanup] - - - id - accountid - - - - - - + + + + + + deployVirtualMachine + [Deploy Virtual Machine in the admin account] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + LoadBalancer-VM-1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + + + + associateIpAddress + [Acquire a non-Source NAT IP Address for the Admin Account] + + + zoneid + globalzoneid + + + account + admin + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip + + + + + + createLoadBalancerRule + [Create Load Balancing Rule -ve test (Private Port 0)] + true + 853 + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 0 + + + publicport + 986 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + [Create Load Balancing Rule -ve test (Private Port 65536)] + true + 853 + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 65536 + + + publicport + 986 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + [Create Load Balancing Rule -ve test (Private Port 65536)] + true + 853 + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 1 + + + publicport + 0 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + [Create Load Balancing Rule -ve test (Public Port 65536)] + true + 853 + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 65535 + + + publicport + 65536 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + + createLoadBalancerRule + 919 [Create Load Balancing Rule - 1(Set of LB Rules for Algorithm Validation begins)] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 9191 + + + publicport + 9191 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + 919-1 [Create Load Balancing Rule - 2] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 9192 + + + publicport + 9192 + + + algorithm + leastconn + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + 919-2 [Create Load Balancing Rule - 3] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 9193 + + + publicport + 9193 + + + algorithm + source + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + true + 919-3 [Create Load Balancing Rule - 4 (Set of LB Rules for Algorithm Validation ends)] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 9194 + + + publicport + 9194 + + + algorithm + roundrobinblahblah + + + account + admin + + + domainid + 1 + + + + + + + + + createLoadBalancerRule + 920-1 [Create Load Balancer Rule] + + + name + lbname + + + publicip + nonsourcenatpublicip + + + privateport + 9201 + + + publicport + 9201 + + + algorithm + source + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + 920-2 [Create a Load Balancer Rule with the Same Name] + + + name + lbname + + + publicip + nonsourcenatpublicip + + + privateport + 9202 + + + publicport + 9202 + + + algorithm + source + + + account + admin + + + domainid + 1 + + + + + + + + createLoadBalancerRule + 921-1 [Create Load Balancer Rule in one account] + + + name + lbname + + + publicip + nonsourcenatpublicip + + + privateport + 921 + + + publicport + 921 + + + algorithm + source + + + account + admin + + + domainid + 1 + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + apiuser@gmail.com + + + firstname + apiuser + + + lastname + apiuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + listUsers + [List the Created User in that account] + + + account + accountvar + + + + + id + userid + + + + + + + registerUserKeys + [Register User Keys] + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar + + + domainid + 1 + + + name + loadbalancersregressionnetwork + + + displaytext + loadbalancersregressionnetwork + + + + + id + networkid + + + + + + deployVirtualMachine + [Deploy Virtual Machine in that Account] + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + displayname + namefordisplay + + + group + group1 + + + account + accountvar + + + domainid + 1 + + + networkids + networkid + + + + + id + vmidtemp + + + + + + associateIpAddress + [Acquire a Non-Source NAT IP Address] + true + + + zoneid + globalzoneid + + + account + accountvar + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip1 + + + + + + createLoadBalancerRule + [Creating a LB Rule with the same name of a rule in another account] + true + 921-2 + + + name + lbname + + + publicip + nonsourcenatpublicip1 + + + privateport + 9211 + + + publicport + 9211 + + + algorithm + source + + + account + accountvar + + + domainid + 1 + + + + + + + + createLoadBalancerRule + 922 [Create Load Balancer Rule with Non-existing ip address -ve case] + true + + + name + + + publicip + 192.168.0.5 + + + privateport + 922 + + + publicport + 922 + + + algorithm + source + + + domainid + 1 + + + account + admin + + + + + + + + createLoadBalancerRule + true + 923 [Create Load Balancer Rule with Ip Address belonging to different user -ve case] + true + + + name + + + publicip + nonsourcenatpublicip1 + + + privateport + 923 + + + publicport + 923 + + + algorithm + source + + + domainid + 1 + + + account + admin + + + + + + + associateIpAddress + [Acquire a non-Source NAT Ip address] + + + zoneid + globalzoneid + + + account + admin + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip + + + + + + createLoadBalancerRule + 924 [Create Load Balancer Rule from non-Source NAT IP Address] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 924 + + + publicport + 924 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + + deleteLoadBalancerRule + [Try to a delete a Non-Existing Load Balancer Rule] + true + 925 + + + id + 10000 + + + + + + + deleteAccount + [Delete account] + + + id + accountid + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + apiuser@gmail.com + + + firstname + apiuser + + + lastname + apiuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + + listUsers + [List the Created User] + + + account + accountvar + + + + + id + userid + + + + + + + registerUserKeys + [Register User Keys] + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar + + + domainid + 1 + + + name + loadbalancersregressionnetwork + + + displaytext + loadbalancersregressionnetwork + + + + + id + networkid + + + + + + deployVirtualMachine + [Deploy Virtual Machine in the account created] + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + displayname + namefordisplay + + + group + group1 + + + account + accountvar + + + networkids + networkid + + + + + + associateIpAddress + [Acquire a non-Source NAT IP Address for that account] + true + + + zoneid + globalzoneid + + + account + accountvar + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip1 + + + + + + createLoadBalancerRule + [Create Load Balancer Rule with the acquired Ip address] + true + + + name + loadb9261 + + + publicip + nonsourcenatpublicip1 + + + privateport + 9261 + + + publicport + 9261 + + + algorithm + source + + + account + accountvar + + + domainid + 1 + + + + + id + lbid1 + + + + + + createLoadBalancerRule + [Create a Second Load Balancer Rule with the Acquired Ip Address] + true + + + name + loadb9262 + + + publicip + nonsourcenatpublicip1 + + + privateport + 9262 + + + publicport + 9262 + + + algorithm + source + + + account + accountvar + + + domainid + 1 + + + + + id + lbid2 + + + + + + createLoadBalancerRule + [Create a Third Load Balancer Rule with the Acquired Ip Address] + true + + + name + loadb9263 + + + publicip + nonsourcenatpublicip1 + + + privateport + 9263 + + + publicport + 9263 + + + algorithm + source + + + account + accountvar + + + domainid + 1 + + + + + id + lbid3 + + + + + + deleteLoadBalancerRule + 926 [Admin allowed to Delete Load Balancer Rule of another account via 8096 port] + + + id + lbid1 + + + + + + + + deleteAccount + [Delete Account as a part of Clean Up] + + + id + accountid + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + apiuser@gmail.com + + + firstname + apiuser + + + lastname + apiuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + + listUsers + [List the Created User of the Account] + + + account + accountvar + + + + + id + userid + + + + + + + registerUserKeys + [Register User Keys] + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + deleteLoadBalancerRule + [Admin allowed to Delete Load Balancer Rule of another account via 8080 port] + true + 927 + + + id + lbid2 + + + + + + + + deleteAccount + [Delete Account as part of Clean Up] + + + id + accountid + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + apiuser@gmail.com + + + firstname + apiuser + + + lastname + apiuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + + listUsers + [List the Created User of the account] + + + account + accountvar + + + + + id + userid + + + + + + + registerUserKeys + [Register User Keys] + + + id + userid + + + domainid + 1 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar + + + domainid + 1 + + + name + loadbalancers-regressionnetwork + + + displaytext + loadbalancers-defaultregressionnetwork + + + + + id + networkid + + + + + + deleteLoadBalancerRule + true + 928 [Regular User can't Delete LBrule belonging to Another User] + true + + + id + lbid3 + + + + + + + deployVirtualMachine + [Deploy a Virtual Machine-1 to check multiple VMs - LB assignment] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + LoadBalancers-VM-1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid1 + + + + + + deployVirtualMachine + [Deploy a Virtual Machine-2 to check multiple VMs - LB assignment] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + LoadBalancers-VM-2 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid2 + + + + + + associateIpAddress + [Acquire a non-Source NAT IP address] + + + zoneid + globalzoneid + + + account + admin + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip + + + + + + createLoadBalancerRule + [Create a Load Balancer Rule] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 931 + + + publicport + 931 + + + algorithm + source + + + domainid + 1 + + + account + admin + + + + + id + lbid + + + + + + assignToLoadBalancerRule + 939 [Assign the First VM to the Load Balancing Rule] + + + id + lbid + + + virtualmachineids + vmid1 + + + + + + assignToLoadBalancerRule + 939 [Assign the Second VM to the Load Balancing Rule] + + + id + lbid + + + virtualmachineids + vmid2 + + + + + + + createLoadBalancerRule + 978 [Create Load Balancing Rule (to test the creation of 2 LB Rules with the same settings )] + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 978 + + + publicport + 978 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + createLoadBalancerRule + 978 [Creation of Second LoadBalancer Rule should Fail] + true + + + name + + + publicip + nonsourcenatpublicip + + + privateport + 978 + + + publicport + 978 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + + listRouters + [Obtain the source nat IP Address - List Routers] + + + zoneid + globalzoneid + + + account + admin + + + domainid + 1 + + + + router + 0 + + publicip + sourcenatpublicip + + + + + + createLoadBalancerRule + 1017 [Create Load Balancer Rule with Source NAT IP Address] + + + name + + + publicip + sourcenatpublicip + + + privateport + 1017 + + + publicport + 1017 + + + algorithm + roundrobin + + + account + admin + + + domainid + 1 + + + + + + + + destroyVirtualMachine + [Destroy VM as a Part of Cleanup] + + + id + vmid1 + + + + + + destroyVirtualMachine + [Destroy vm as a part of cleanup] + + + id + vmid2 + + + + + + destroyVirtualMachine + [Destroy vm as a part of cleanup] + + + id + vmid + + + + + + deleteAccount + [Deleting Account as a part of cleanup] + + + id + accountid + + + + + + diff --git a/test/metadata/func/localstorage_volume_test.xml b/test/metadata/func/localstorage_volume_test.xml index aa1bbcb42923..99157795d978 100644 --- a/test/metadata/func/localstorage_volume_test.xml +++ b/test/metadata/func/localstorage_volume_test.xml @@ -1,153 +1,153 @@ - - - - deployVirtualMachine - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - - - id - vmid - - - - - - - createVolume - 1027 - true - - - account - admin - - - domainid - 1 - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datadiskvolume - - - - - - detachVolume - 1032 - true - - - id - datadiskvolume - - - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootdiskvolume - - - - - - detachVolume - 1034 - true - - - id - rootdiskvolume - - - - - - - - - + + + + deployVirtualMachine + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + + + id + vmid + + + + + + + createVolume + 1027 + true + + + account + admin + + + domainid + 1 + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datadiskvolume + + + + + + detachVolume + 1032 + true + + + id + datadiskvolume + + + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootdiskvolume + + + + + + detachVolume + 1034 + true + + + id + rootdiskvolume + + + + + + + + + diff --git a/test/metadata/func/mgmtvmsync.xml b/test/metadata/func/mgmtvmsync.xml index f2b884e8a21c..194a10c14019 100644 --- a/test/metadata/func/mgmtvmsync.xml +++ b/test/metadata/func/mgmtvmsync.xml @@ -1,651 +1,651 @@ - - - - - - deployVirtualMachine - deploy First VM test case - - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - displayname - FirstVM - - - account - admin - - - domainid - 1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid_1st - - - name - vmname_1st - - - hostid - hostid_1st - - - - - - deployVirtualMachine - deploy Second VM test case - - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - displayname - SecondVM - - - account - admin - - - domainid - 1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid_2nd - - - name - vmname_2nd - - - hostid - hostid_2nd - - - - - - - - updateVirtualMachine - HA disable first virtual machine - - - id - vmid_1st - - - displayname - HAdisabledFirstVirtualMachine - - - haenable - false - - - - - - - updateVirtualMachine - HA disable second virtual machine - - - id - vmid_2nd - - - displayname - HAdisabledSecondVirtualMachine - - - haenable - false - - - - - - - - listHosts - Obtain IP address of ha disabled VM's Host Test Case - - - id - hostid_1st - - - - - ipaddress - ipaddress_1st - - - - - - - - listHosts - Obtain IP address of ha enabled VM's Host Test Case - - - id - hostid_2nd - - - - - ipaddress - ipaddress_2nd - - - - - - - select instance_name from vm_instance - true - Obtaining the internal instance name for the ha disabled VM Test Case - - - id - vmid_1st - - - - - instance_name - instance_name_1st - - - - - - select instance_name from vm_instance - true - Obtaining the internal instance name for the ha enabled VM Test Case - - - id - vmid_2nd - - - - - instance_name - instance_name_2nd - - - - - - - - ms.sh - - Stopping management server - - - h - ms1 - - - o - stop - - - - - - - - - - killvm.sh - Kill the First HA disabled VM - - - - - n - instance_name_1st - - - - h - ipaddress_1st - - - - - - killvm.sh - Kill the Second HA disabled VM - - - - - n - instance_name_2nd - - - - h - ipaddress_2nd - - - - - - - sleep.sh - - Sleep for some time - - - s - 100 - - - - - - - - - ms.sh - - Starting management server - - - h - ms1 - - - o - start - - - - - - - sleep.sh - - Sleep for some time - - - s - 100 - - - - - - - - listVirtualMachines - Check the stopped status of First virtual machine with ha disabled - - - id - vmid_1st - - - - - state - Stopped - - - - - - listVirtualMachines - Check the stopped status of Second virtual machine with ha disabled - - - id - vmid_2nd - - - - - state - Stopped - - - - - - - - - destroyVirtualMachine - Destroy the First vm as a part of cleanup - - - id - vmid_1st - - - - - - destroyVirtualMachine - Destroy the Second vm as a part of cleanup - - - id - vmid_2nd - - - - - - - - - deployVirtualMachine - deploy VM test case - - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - displayname - FirstVM - - - account - admin - - - domainid - 1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid_1st - - - name - vmname_1st - - - hostid - hostid_1st - - - - - - - listHosts - Obtain IP address of the VM's Host Test Case - - - id - hostid_1st - - - - - ipaddress - ipaddress_1st - - - - - - - - select instance_name from vm_instance - true - Obtaining the internal instance name for the VM Test Case - - - id - vmid_1st - - - - - instance_name - instance_name_1st - - - - - - - ms.sh - - Stopping management server - - - h - ms1 - - - o - stop - - - - - - - - shutdown.sh - Shut down the VM Test Case - - - - - n - instance_name_1st - - - - h - ipaddress_1st - - - - - - - sleep.sh - - Sleep for some time - - - s - 60 - - - - - - - - ms.sh - - Starting management server - - - h - ms1 - - - o - start - - - - - - - sleep.sh - - Sleep for some time - - - s - 60 - - - - - - - - listVirtualMachines - Check the stopped status of virtual machine - - - id - vmid_1st - - - - - state - Stopped - - - - - - listHosts - List host that is presumed to have the VM Test Case - - - id - hostid_1st - - - - - ipaddress - ipaddress_1st - - - - - - listvm.sh - - VM Not Found on the host Test Case - true - - - h - ipaddress_1st - - - n - instance_name_1st - - - - - + + + + + + deployVirtualMachine + deploy First VM test case + + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + displayname + FirstVM + + + account + admin + + + domainid + 1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid_1st + + + name + vmname_1st + + + hostid + hostid_1st + + + + + + deployVirtualMachine + deploy Second VM test case + + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + displayname + SecondVM + + + account + admin + + + domainid + 1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid_2nd + + + name + vmname_2nd + + + hostid + hostid_2nd + + + + + + + + updateVirtualMachine + HA disable first virtual machine + + + id + vmid_1st + + + displayname + HAdisabledFirstVirtualMachine + + + haenable + false + + + + + + + updateVirtualMachine + HA disable second virtual machine + + + id + vmid_2nd + + + displayname + HAdisabledSecondVirtualMachine + + + haenable + false + + + + + + + + listHosts + Obtain IP address of ha disabled VM's Host Test Case + + + id + hostid_1st + + + + + ipaddress + ipaddress_1st + + + + + + + + listHosts + Obtain IP address of ha enabled VM's Host Test Case + + + id + hostid_2nd + + + + + ipaddress + ipaddress_2nd + + + + + + + select instance_name from vm_instance + true + Obtaining the internal instance name for the ha disabled VM Test Case + + + id + vmid_1st + + + + + instance_name + instance_name_1st + + + + + + select instance_name from vm_instance + true + Obtaining the internal instance name for the ha enabled VM Test Case + + + id + vmid_2nd + + + + + instance_name + instance_name_2nd + + + + + + + + ms.sh + + Stopping management server + + + h + ms1 + + + o + stop + + + + + + + + + + killvm.sh + Kill the First HA disabled VM + + + + + n + instance_name_1st + + + + h + ipaddress_1st + + + + + + killvm.sh + Kill the Second HA disabled VM + + + + + n + instance_name_2nd + + + + h + ipaddress_2nd + + + + + + + sleep.sh + + Sleep for some time + + + s + 100 + + + + + + + + + ms.sh + + Starting management server + + + h + ms1 + + + o + start + + + + + + + sleep.sh + + Sleep for some time + + + s + 100 + + + + + + + + listVirtualMachines + Check the stopped status of First virtual machine with ha disabled + + + id + vmid_1st + + + + + state + Stopped + + + + + + listVirtualMachines + Check the stopped status of Second virtual machine with ha disabled + + + id + vmid_2nd + + + + + state + Stopped + + + + + + + + + destroyVirtualMachine + Destroy the First vm as a part of cleanup + + + id + vmid_1st + + + + + + destroyVirtualMachine + Destroy the Second vm as a part of cleanup + + + id + vmid_2nd + + + + + + + + + deployVirtualMachine + deploy VM test case + + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + displayname + FirstVM + + + account + admin + + + domainid + 1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid_1st + + + name + vmname_1st + + + hostid + hostid_1st + + + + + + + listHosts + Obtain IP address of the VM's Host Test Case + + + id + hostid_1st + + + + + ipaddress + ipaddress_1st + + + + + + + + select instance_name from vm_instance + true + Obtaining the internal instance name for the VM Test Case + + + id + vmid_1st + + + + + instance_name + instance_name_1st + + + + + + + ms.sh + + Stopping management server + + + h + ms1 + + + o + stop + + + + + + + + shutdown.sh + Shut down the VM Test Case + + + + + n + instance_name_1st + + + + h + ipaddress_1st + + + + + + + sleep.sh + + Sleep for some time + + + s + 60 + + + + + + + + ms.sh + + Starting management server + + + h + ms1 + + + o + start + + + + + + + sleep.sh + + Sleep for some time + + + s + 60 + + + + + + + + listVirtualMachines + Check the stopped status of virtual machine + + + id + vmid_1st + + + + + state + Stopped + + + + + + listHosts + List host that is presumed to have the VM Test Case + + + id + hostid_1st + + + + + ipaddress + ipaddress_1st + + + + + + listvm.sh + + VM Not Found on the host Test Case + true + + + h + ipaddress_1st + + + n + instance_name_1st + + + + + diff --git a/test/metadata/func/private_templates.xml b/test/metadata/func/private_templates.xml index c56eec4a5dc4..2c9430c429f0 100644 --- a/test/metadata/func/private_templates.xml +++ b/test/metadata/func/private_templates.xml @@ -1,911 +1,911 @@ - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - privateuser@gmail.com - - - firstname - privateuser - - - lastname - privateuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountvar - - - - - id - userid - - - - - - - registerUserKeys - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar - - - domainid - 1 - - - name - privatetemplates_regressionnetwork - - - displaytext - privatetemplates_regressionnetwork - - - - - id - networkid - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - PrivateTemplate-VM-1 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - createTemplate - true - 507 - - - volumeid - rootvolumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - privatetemplateid - - - - - - - - startVirtualMachine - true - - - id - vmid - - - - - - createTemplate - true - true - 508 - - - volumeid - rootvolumeid - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - - - destroyVirtualMachine - true - - - id - vmid - - - - - - createTemplate - true - true - 509 - - - volumeid - rootvolumeid - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - - - deployVirtualMachine - true - 510 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - privatetemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - PrivateTemplate-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - templateid - privatetemplateid - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - createTemplate - true - - - volumeid - rootvolumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - privatetemplateid - - - - - - deployVirtualMachine - true - 511 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - privatetemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - PrivateTemplate-VM-3 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - templateid - privatetemplateid - - - - - - - createTemplate - true - - - volumeid - rootvolumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - privatetemplateid - - - - - - createTemplate - true - 512 - - - volumeid - rootvolumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - - - - - - - createAccount - [Create Different Account and a User for the account] - - - account - accountvar1 - - - accounttype - 0 - - - domainid - 1 - - - email - jack@gmail.com - - - firstname - jack12 - - - lastname - jacklastname - - - username - usernameparam1 - - - password - jack12 - - - - - id - accountid1 - - - name - accountvar1 - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountvar1 - - - - - id - userid1 - - - - - - - registerUserKeys - - - id - userid1 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar1 - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar1 - - - domainid - 1 - - - displayname - PrivateTemplate-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid1 - - - - - - createTemplate - true - 513 - - - volumeid - rootvolumeid1 - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - - - - - - deleteAccount - Deleting First Account as a part of cleanup - - - id - accountvar - - - - - - deleteAccount - Deleting Second Account as a part of cleanup - - - id - accountvar1 - - - - - - - + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + privateuser@gmail.com + + + firstname + privateuser + + + lastname + privateuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountvar + + + + + id + userid + + + + + + + registerUserKeys + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar + + + domainid + 1 + + + name + privatetemplates_regressionnetwork + + + displaytext + privatetemplates_regressionnetwork + + + + + id + networkid + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + PrivateTemplate-VM-1 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + createTemplate + true + 507 + + + volumeid + rootvolumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + privatetemplateid + + + + + + + + startVirtualMachine + true + + + id + vmid + + + + + + createTemplate + true + true + 508 + + + volumeid + rootvolumeid + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + + + destroyVirtualMachine + true + + + id + vmid + + + + + + createTemplate + true + true + 509 + + + volumeid + rootvolumeid + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + + + deployVirtualMachine + true + 510 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + privatetemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + PrivateTemplate-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + templateid + privatetemplateid + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + createTemplate + true + + + volumeid + rootvolumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + privatetemplateid + + + + + + deployVirtualMachine + true + 511 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + privatetemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + PrivateTemplate-VM-3 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + templateid + privatetemplateid + + + + + + + createTemplate + true + + + volumeid + rootvolumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + privatetemplateid + + + + + + createTemplate + true + 512 + + + volumeid + rootvolumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + + + + + + + createAccount + [Create Different Account and a User for the account] + + + account + accountvar1 + + + accounttype + 0 + + + domainid + 1 + + + email + jack@gmail.com + + + firstname + jack12 + + + lastname + jacklastname + + + username + usernameparam1 + + + password + jack12 + + + + + id + accountid1 + + + name + accountvar1 + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountvar1 + + + + + id + userid1 + + + + + + + registerUserKeys + + + id + userid1 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar1 + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar1 + + + domainid + 1 + + + displayname + PrivateTemplate-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid1 + + + + + + createTemplate + true + 513 + + + volumeid + rootvolumeid1 + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + + + + + + deleteAccount + Deleting First Account as a part of cleanup + + + id + accountvar + + + + + + deleteAccount + Deleting Second Account as a part of cleanup + + + id + accountvar1 + + + + + + + diff --git a/test/metadata/func/regression.xml b/test/metadata/func/regression.xml index eebf13ff74c1..18d2f516780b 100644 --- a/test/metadata/func/regression.xml +++ b/test/metadata/func/regression.xml @@ -1,4443 +1,4443 @@ - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - admin - - - lastname - adminlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmavailable - Unlimited - - - vmlimit - Unlimited - - - vmtotal - 0 - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - - id - userid - - - username - usernameparam - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - - - - - - - createUser - 3 [Create User Command] - - - account - accountname - - - accounttype - 1 - - - username - secondusernameparam - - - password - password - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - domainid - 1 - - - - - - id - seconduserid - - - username - usernameparam - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - created - createtimestamp - - - state - enabled - - - - - - - listAccounts - 950 [List the Account of the User created in (3)] - - - name - accountname - - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - updateUser - 6 [Update the User created in (3)] - - - id - userid - - - username - newuser - - - password - password - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - listUsers - 5 [List the Updated User updated in (6)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - - - - - disableUser - 428 [Disable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - disabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - enableUser - 9 [Enable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - disableAccount - [Disable the Account] - - - account - accountname - - - domainid - 1 - - - lock - true - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - locked - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - 1 - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - disableAccount - [Lock the Account] - - - account - accountname - - - domainid - 1 - - - lock - false - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - disabled - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - 1 - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - createDomain - 10 [Create Domain] - - - name - domainname - - - - - - name - domainname - - - id - newdomainid - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - listDomains - 11 [List Domains] - - - name - domainname - - - - - - id - newdomainid - - - name - domainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - updateDomain - 952 [Update the Domain created in (10)] - - - id - newdomainid - - - name - newdomainname - - - - - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - - listDomainChildren - - [List One of the Children of the Parent Domain] - - - id - 1 - - - - - domain - 0 - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - deleteDomain - 654 [Delete Domain created in (10)] - - - id - newdomainid - - - cleanup - true - - - - - - success - true - - - - - - - - - listTemplates - 27 [List Templates - List System Template] - - - templatefilter - self - - - id - 1 - - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - passwordenabled - debian50templatepasswordenabled - - - format - VHD - - - - isfeatured - false - - - crossZones - true - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - size - debian50templatesize - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - domain - ROOT - - - domainid - 1 - - - - - - - - listTemplates - - Verify All the Parameters of the Listed Template - - - - name - debian50templatename - - - templatefilter - self - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - isfeatured - false - - - passwordenabled - debian50templatepasswordenabled - - - crossZones - true - - - format - VHD - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - debian50templatesize - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - - - - - - - listServiceOfferings - 28 [List the Small Instance Service Offering] - - - id - globalserviceofferingid - - - - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - listServiceOfferings - 876 [Verify the First Service Offering] - - - name - smallofferingname - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - - - listZones - 39 [List the Global Zone with id-2] - - zone - 0 - - id - globalzoneid - - - name - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - internaldns1 - internaldns1 - - - vlan - - - guestcidraddress - - - networktype - Advanced - - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - - deployVirtualMachine - 13 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - namefordisplay - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - globalserviceofferingid - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - namefordisplay - - - group - group1 - - - - - - - - listVirtualMachines - 957 - false - - - name - vmname - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - namefordisplay - - - group - group1 - - - - - - - listRouters - 35 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - router - 0 - - id - routerid - - - publicip - sourcenatpublicip - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - routerpublicmacaddress - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - created - - - hostid - - - state - Running - - - - - - - associateIpAddress - 546 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - nonsourcenatpublicipid - - - ipaddress - nonsourcenatpublicip - - - allocated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - issourcenat - false - - - - - - - - - createLoadBalancerRule - 986 - - - name - lbname - - - publicipid - nonsourcenatpublicipid - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - - - id - lbid - - - name - lbname - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - publicipid - nonsourcenatpublicipid - - - publicip - sourcenatpublicip - - - account - accountname - - - - - - - - listLoadBalancerRules - 988 - false - - - account - accountname - - - domainId - 1 - - - - - - - - assignToLoadBalancerRule - 989 - - - id - lbid - - - virtualmachineids - vmid - - - - - - - deleteLoadBalancerRule - 987 - - - id - lbid - - - - - - listLoadBalancerRules - true - - - - createLoadBalancerRule - - - name - lbname - - - publicipid - nonsourcenatpublicipid - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - - - id - lbid - - - name - lbname - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - publicipid - nonsourcenatpublicipid - - - publicip - sourcenatpublicip - - - account - accountname - - - - - - assignToLoadBalancerRule - - - id - lbid - - - virtualmachineids - vmid - - - - - - - - listLoadBalancerRuleInstances - 991 - false - - - id - lbid - - - - - - - removeFromLoadBalancerRule - 990 - - - id - lbid - - - virtualmachineids - vmid - - - account - accountname - - - - - - listLoadBalancerRuleInstances - true - - - id - lbid - - - - - - - - - - - - rebootVirtualMachine - 16 - - - id - vmid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Running - - - - - - - - stopVirtualMachine - 17 - - - id - vmid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - - - - - detachVolume - - - id - datavolumeid - - - - - - listVolumes - true - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - 1026 - - - account - accountname - - - domainid - 1 - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid - - - - - - - - attachVolume - - - id - datavolumeid - - - virtualmachineid - vmid - - - - - - listVolumes - false - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - - - account - accountname - - - domainid - 1 - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid1 - - - - - - deleteVolume - - - id - datavolumeid1 - - - - - - listVolumes - true - - - id - datavolumeid1 - - - - - - - - - createSnapshot - - - volumeid - rootvolumeid - - - - - id - snapshotid1 - - - volumeid - rootvolumeid - - - created - - - name - - - account - accountname - - - - - - listSnapshots - false - - - volumeid - rootvolumeid - - - - - - - deleteSnapshot - 24 - - - id - snapshotid1 - - - - - - - listSnapshots - 26 - true - - - volumeid - rootvolumeid - - - - - - - createSnapshot - - - volumeid - rootvolumeid - - - - - id - snapshotid1 - - - - - - createTemplate - 958 - - - snapshotid - snapshotid1 - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 1 - - - - - id - privatetemplateid - - - name - privatetemplatename - - - ispublic - false - - - isready - true - - - created - - - passwordenabled - false - - - - - - - listTemplates - 27 - - - name - privatetemplatename - - - templatefilter - self - - - - - id - privatetemplateid - - - name - privatetemplatename - - - ispublic - false - - - isready - true - - - created - - - passwordenabled - false - - - - - - - updateTemplate - Update template - - - id - privatetemplateid - - - displaytext - lalala - - - name - blah - - - - - - listTemplates - List updated template - - - id - privatetemplateid - - - templatefilter - self - - - - - displaytext - lalala - - - - - - - - listTemplatePermissions - list template permissions command - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - false - - - - - - - - updateTemplatePermissions - update template permissions - - - id - privatetemplateid - - - ispublic - true - - - - - - listTemplates - List template with updated permission - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - true - - - - - - - - deleteTemplate - 959 - - - id - privatetemplateid - - - - - - listTemplates - true - - - name - privatetemplatename - - - templatefilter - self - - - - - - - - - changeServiceForVirtualMachine - 20 - - - id - vmid - - - serviceofferingid - mediumglobalserviceofferingid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - serviceofferingid - 2 - - - - - - - - resetPasswordForVirtualMachine - 19 - - - id - vmid - - - - - id - vmid - - - password - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - startVirtualMachine - 18 - - - id - vmid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - 2 - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Running - - - - - - - updateVirtualMachine - - - id - vmid - - - haenable - true - - - - - - listVirtualMachines - 21 - - - account - accountname - - - name - vmname - - - - - haenable - true - - - - - - updateVirtualMachine - - - id - vmid - - - haenable - false - - - - - - listVirtualMachines - 21 - - - account - accountname - - - name - vmname - - - - - haenable - false - - - - - - - - - listPublicIpAddresses - 547 - - - account - accountname - - - - - - - - - - disassociateIpAddress - 548 - - - ipaddress - nonsourcenatpublicip - - - account - accountname - - - - - - - rebootRouter - 36 - - - id - routerid - - - - - - - stopRouter - 37 - - - id - routerid - - - - - - - startRouter - 38 - - - id - routerid - - - - - id - routerid - - - publicip - sourcenatpublicip - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - networkdomain - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - routerpublicmacaddress - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - created - - - hostid - - - state - Running - - - - - - - listEvents - 40 - - - account - accountname - - - - - - - destroyVirtualMachine - 14 - - - id - vmid - - - - - - listVirtualMachines - - - id - vmid - - - - - state - Destroyed - - - - - - - recoverVirtualMachine - 22 - - - id - vmid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - - listConfigurations - 549 - false - - - - - listConfigurations - 878 - false - - - category - advanced - - - - - - - - updateConfiguration - 550 - - - name - update.wait - - - value - 35 - - - - - - listConfigurations - - - name - update.wait - - - - - value - 35 - - - - - - - - listPods - 552 - - - - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname1 - - - accounttype - 1 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - admin - - - lastname - adminlastname - - - username - usernameparam - - - password - password - - - - - - listAccounts - - - name - accountname1 - - - - - id - accountid1 - - - - - - updateAccount - 951 - - - account - accountname1 - - - domainid - 1 - - - newname - updatedname - - - - - - listAccounts - - - name - updatedname - - - - - id - accountid1 - - - name - updatedname - - - - - - - disableAccount - 7 - - - account - updatedname - - - domainid - 1 - - - lock - true - - - - - - - - - enableAccount - 8 - - - account - updatedname - - - domainid - 1 - - - - - - - - updateResourceLimit - 953 - - - resourcetype - 1 - - - max - 300 - - - domainid - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 300 - - - - - - - listResourceLimits - true - 956 - - - domainid - 1 - - - resourcetype - 1 - - - - - max - 300 - - - - - - - updateResourceLimit - 954 - - - domainid - 1 - - - resourcetype - 1 - - - max - 65 - - - - - - listResourceLimits - - - domainid - 1 - - - resourcetype - 1 - - - - - max - 65 - - - - - - - - createDiskOffering - 932 - - - name - newdiskname - - - description - newdiskdesc - - - disksize - 1024 - - - displaytext - volumedisplay - - - - - id - diskid - - - domainid - 1 - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1099511627776 - - - - - - - - - listDiskOfferings - 934 - false - - - domainid - 1 - - - name - newdisk - - - - - - - - updateDiskOffering - - - id - diskid - - - name - newdiskname - - - - - - listDiskOfferings - - - domainid - 1 - - - name - newdiskname - - - - - name - newdiskname - - - - - - - - deleteDiskOffering - 933 - - - id - diskid - - - - - - listDiskOfferings - true - - - domainid - 1 - - - id - diskid - - - - - - - listAlerts - 750 - false - - - - - listCapacity - 974 - false - - - - - listHosts - false - 975 - - - - - - listIsos - 960 - true - - - - - createServiceOffering - 962 - - - name - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - - - id - soid - - - - - - listServiceOfferings - - - id - soid - - - - - id - newofferingid - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - - - - - - updateServiceOffering - 964 - - - id - newofferingid - - - name - - - displaytext - display - - - - - - - listServiceOfferings - - - id - newofferingid - - - - - id - newofferingid - - - displaytext - display - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - - - - - - deleteServiceOffering - 963-1 - - - id - newofferingid - - - - - - listServiceOfferings - 963-2 - true - - - id - newofferingid - - - - - - - - registerIso - Register iso - - - name - isoname - - - displaytext - isodisplay - - - url - http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso - - - ostypeid - 53 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - privateisoid - - - created - - - ispublic - false - - - bootable - true - - - isready - false - - - isfeatured - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - listIsos - List isos - false - - - id - privateisoid - - - - - - - registerTemplate - Register template - - - name - registername - - - displaytext - registerdisplay - - - url - globaltemplateurl - - - ostypeid - 1 - - - format - vhd - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - id - systemtemplateid - - - created - - - ispublic - false - - - isfeatured - false - - - passwordenabled - false - - - format - VHD - - - isready - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - - listSystemVms - List system vms - - - domainid - 1 - - - - systemvm - 0 - - id - consoleid - - - publicip - - - zoneid - - - zonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - - - privatemacaddress - - - created - - - hostid - - - state - Running - - - activeviewersessions - 0 - - - - - - - rebootSystemVm - Reboot system vm - - - id - consoleid - - - - - - - stopSystemVm - Stop system vm - - - id - consoleid - - - - - - - startSystemVm - Start system vm - - - id - consoleid - - - - - - - - deleteIso - Delete iso - - - id - privateisoid - - - - - - listIsos - List isos - true - - - id - privateisoid - - - - - - - deleteTemplate - Delete template - - - id - systemtemplateid - - - - - - listTemplates - List templates - true - - - id - systemtemplateid - - - templatefilter - self - - - - - - - deleteAccount - Deleting account - - - id - accountid - - - - - - + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + admin + + + lastname + adminlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmavailable + Unlimited + + + vmlimit + Unlimited + + + vmtotal + 0 + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + + id + userid + + + username + usernameparam + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + + + + + + + createUser + 3 [Create User Command] + + + account + accountname + + + accounttype + 1 + + + username + secondusernameparam + + + password + password + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + domainid + 1 + + + + + + id + seconduserid + + + username + usernameparam + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + created + createtimestamp + + + state + enabled + + + + + + + listAccounts + 950 [List the Account of the User created in (3)] + + + name + accountname + + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + updateUser + 6 [Update the User created in (3)] + + + id + userid + + + username + newuser + + + password + password + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + listUsers + 5 [List the Updated User updated in (6)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + + + + + disableUser + 428 [Disable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + disabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + enableUser + 9 [Enable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + disableAccount + [Disable the Account] + + + account + accountname + + + domainid + 1 + + + lock + true + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + locked + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + 1 + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + disableAccount + [Lock the Account] + + + account + accountname + + + domainid + 1 + + + lock + false + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + disabled + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + 1 + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + createDomain + 10 [Create Domain] + + + name + domainname + + + + + + name + domainname + + + id + newdomainid + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + listDomains + 11 [List Domains] + + + name + domainname + + + + + + id + newdomainid + + + name + domainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + updateDomain + 952 [Update the Domain created in (10)] + + + id + newdomainid + + + name + newdomainname + + + + + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + + listDomainChildren + - [List One of the Children of the Parent Domain] + + + id + 1 + + + + + domain + 0 + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + deleteDomain + 654 [Delete Domain created in (10)] + + + id + newdomainid + + + cleanup + true + + + + + + success + true + + + + + + + + + listTemplates + 27 [List Templates - List System Template] + + + templatefilter + self + + + id + 1 + + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + passwordenabled + debian50templatepasswordenabled + + + format + VHD + + + + isfeatured + false + + + crossZones + true + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + size + debian50templatesize + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + domain + ROOT + + + domainid + 1 + + + + + + + + listTemplates + - Verify All the Parameters of the Listed Template - + + + name + debian50templatename + + + templatefilter + self + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + isfeatured + false + + + passwordenabled + debian50templatepasswordenabled + + + crossZones + true + + + format + VHD + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + debian50templatesize + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + + + + + + + listServiceOfferings + 28 [List the Small Instance Service Offering] + + + id + globalserviceofferingid + + + + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + listServiceOfferings + 876 [Verify the First Service Offering] + + + name + smallofferingname + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + + + listZones + 39 [List the Global Zone with id-2] + + zone + 0 + + id + globalzoneid + + + name + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + internaldns1 + internaldns1 + + + vlan + + + guestcidraddress + + + networktype + Advanced + + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + + deployVirtualMachine + 13 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + namefordisplay + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + globalserviceofferingid + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + namefordisplay + + + group + group1 + + + + + + + + listVirtualMachines + 957 + false + + + name + vmname + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + namefordisplay + + + group + group1 + + + + + + + listRouters + 35 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + router + 0 + + id + routerid + + + publicip + sourcenatpublicip + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + routerpublicmacaddress + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + created + + + hostid + + + state + Running + + + + + + + associateIpAddress + 546 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + nonsourcenatpublicipid + + + ipaddress + nonsourcenatpublicip + + + allocated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + issourcenat + false + + + + + + + + + createLoadBalancerRule + 986 + + + name + lbname + + + publicipid + nonsourcenatpublicipid + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + + + id + lbid + + + name + lbname + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + publicipid + nonsourcenatpublicipid + + + publicip + sourcenatpublicip + + + account + accountname + + + + + + + + listLoadBalancerRules + 988 + false + + + account + accountname + + + domainId + 1 + + + + + + + + assignToLoadBalancerRule + 989 + + + id + lbid + + + virtualmachineids + vmid + + + + + + + deleteLoadBalancerRule + 987 + + + id + lbid + + + + + + listLoadBalancerRules + true + + + + createLoadBalancerRule + + + name + lbname + + + publicipid + nonsourcenatpublicipid + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + + + id + lbid + + + name + lbname + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + publicipid + nonsourcenatpublicipid + + + publicip + sourcenatpublicip + + + account + accountname + + + + + + assignToLoadBalancerRule + + + id + lbid + + + virtualmachineids + vmid + + + + + + + + listLoadBalancerRuleInstances + 991 + false + + + id + lbid + + + + + + + removeFromLoadBalancerRule + 990 + + + id + lbid + + + virtualmachineids + vmid + + + account + accountname + + + + + + listLoadBalancerRuleInstances + true + + + id + lbid + + + + + + + + + + + + rebootVirtualMachine + 16 + + + id + vmid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Running + + + + + + + + stopVirtualMachine + 17 + + + id + vmid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + + + + + detachVolume + + + id + datavolumeid + + + + + + listVolumes + true + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + 1026 + + + account + accountname + + + domainid + 1 + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid + + + + + + + + attachVolume + + + id + datavolumeid + + + virtualmachineid + vmid + + + + + + listVolumes + false + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + + + account + accountname + + + domainid + 1 + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid1 + + + + + + deleteVolume + + + id + datavolumeid1 + + + + + + listVolumes + true + + + id + datavolumeid1 + + + + + + + + + createSnapshot + + + volumeid + rootvolumeid + + + + + id + snapshotid1 + + + volumeid + rootvolumeid + + + created + + + name + + + account + accountname + + + + + + listSnapshots + false + + + volumeid + rootvolumeid + + + + + + + deleteSnapshot + 24 + + + id + snapshotid1 + + + + + + + listSnapshots + 26 + true + + + volumeid + rootvolumeid + + + + + + + createSnapshot + + + volumeid + rootvolumeid + + + + + id + snapshotid1 + + + + + + createTemplate + 958 + + + snapshotid + snapshotid1 + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 1 + + + + + id + privatetemplateid + + + name + privatetemplatename + + + ispublic + false + + + isready + true + + + created + + + passwordenabled + false + + + + + + + listTemplates + 27 + + + name + privatetemplatename + + + templatefilter + self + + + + + id + privatetemplateid + + + name + privatetemplatename + + + ispublic + false + + + isready + true + + + created + + + passwordenabled + false + + + + + + + updateTemplate + Update template + + + id + privatetemplateid + + + displaytext + lalala + + + name + blah + + + + + + listTemplates + List updated template + + + id + privatetemplateid + + + templatefilter + self + + + + + displaytext + lalala + + + + + + + + listTemplatePermissions + list template permissions command + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + false + + + + + + + + updateTemplatePermissions + update template permissions + + + id + privatetemplateid + + + ispublic + true + + + + + + listTemplates + List template with updated permission + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + true + + + + + + + + deleteTemplate + 959 + + + id + privatetemplateid + + + + + + listTemplates + true + + + name + privatetemplatename + + + templatefilter + self + + + + + + + + + changeServiceForVirtualMachine + 20 + + + id + vmid + + + serviceofferingid + mediumglobalserviceofferingid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + serviceofferingid + 2 + + + + + + + + resetPasswordForVirtualMachine + 19 + + + id + vmid + + + + + id + vmid + + + password + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + startVirtualMachine + 18 + + + id + vmid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + 2 + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Running + + + + + + + updateVirtualMachine + + + id + vmid + + + haenable + true + + + + + + listVirtualMachines + 21 + + + account + accountname + + + name + vmname + + + + + haenable + true + + + + + + updateVirtualMachine + + + id + vmid + + + haenable + false + + + + + + listVirtualMachines + 21 + + + account + accountname + + + name + vmname + + + + + haenable + false + + + + + + + + + listPublicIpAddresses + 547 + + + account + accountname + + + + + + + + + + disassociateIpAddress + 548 + + + ipaddress + nonsourcenatpublicip + + + account + accountname + + + + + + + rebootRouter + 36 + + + id + routerid + + + + + + + stopRouter + 37 + + + id + routerid + + + + + + + startRouter + 38 + + + id + routerid + + + + + id + routerid + + + publicip + sourcenatpublicip + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + networkdomain + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + routerpublicmacaddress + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + created + + + hostid + + + state + Running + + + + + + + listEvents + 40 + + + account + accountname + + + + + + + destroyVirtualMachine + 14 + + + id + vmid + + + + + + listVirtualMachines + + + id + vmid + + + + + state + Destroyed + + + + + + + recoverVirtualMachine + 22 + + + id + vmid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + + listConfigurations + 549 + false + + + + + listConfigurations + 878 + false + + + category + advanced + + + + + + + + updateConfiguration + 550 + + + name + update.wait + + + value + 35 + + + + + + listConfigurations + + + name + update.wait + + + + + value + 35 + + + + + + + + listPods + 552 + + + + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname1 + + + accounttype + 1 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + admin + + + lastname + adminlastname + + + username + usernameparam + + + password + password + + + + + + listAccounts + + + name + accountname1 + + + + + id + accountid1 + + + + + + updateAccount + 951 + + + account + accountname1 + + + domainid + 1 + + + newname + updatedname + + + + + + listAccounts + + + name + updatedname + + + + + id + accountid1 + + + name + updatedname + + + + + + + disableAccount + 7 + + + account + updatedname + + + domainid + 1 + + + lock + true + + + + + + + + + enableAccount + 8 + + + account + updatedname + + + domainid + 1 + + + + + + + + updateResourceLimit + 953 + + + resourcetype + 1 + + + max + 300 + + + domainid + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 300 + + + + + + + listResourceLimits + true + 956 + + + domainid + 1 + + + resourcetype + 1 + + + + + max + 300 + + + + + + + updateResourceLimit + 954 + + + domainid + 1 + + + resourcetype + 1 + + + max + 65 + + + + + + listResourceLimits + + + domainid + 1 + + + resourcetype + 1 + + + + + max + 65 + + + + + + + + createDiskOffering + 932 + + + name + newdiskname + + + description + newdiskdesc + + + disksize + 1024 + + + displaytext + volumedisplay + + + + + id + diskid + + + domainid + 1 + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1099511627776 + + + + + + + + + listDiskOfferings + 934 + false + + + domainid + 1 + + + name + newdisk + + + + + + + + updateDiskOffering + + + id + diskid + + + name + newdiskname + + + + + + listDiskOfferings + + + domainid + 1 + + + name + newdiskname + + + + + name + newdiskname + + + + + + + + deleteDiskOffering + 933 + + + id + diskid + + + + + + listDiskOfferings + true + + + domainid + 1 + + + id + diskid + + + + + + + listAlerts + 750 + false + + + + + listCapacity + 974 + false + + + + + listHosts + false + 975 + + + + + + listIsos + 960 + true + + + + + createServiceOffering + 962 + + + name + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + + + id + soid + + + + + + listServiceOfferings + + + id + soid + + + + + id + newofferingid + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + + + + + + updateServiceOffering + 964 + + + id + newofferingid + + + name + + + displaytext + display + + + + + + + listServiceOfferings + + + id + newofferingid + + + + + id + newofferingid + + + displaytext + display + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + + + + + + deleteServiceOffering + 963-1 + + + id + newofferingid + + + + + + listServiceOfferings + 963-2 + true + + + id + newofferingid + + + + + + + + registerIso + Register iso + + + name + isoname + + + displaytext + isodisplay + + + url + http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso + + + ostypeid + 53 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + privateisoid + + + created + + + ispublic + false + + + bootable + true + + + isready + false + + + isfeatured + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + listIsos + List isos + false + + + id + privateisoid + + + + + + + registerTemplate + Register template + + + name + registername + + + displaytext + registerdisplay + + + url + globaltemplateurl + + + ostypeid + 1 + + + format + vhd + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + id + systemtemplateid + + + created + + + ispublic + false + + + isfeatured + false + + + passwordenabled + false + + + format + VHD + + + isready + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + + listSystemVms + List system vms + + + domainid + 1 + + + + systemvm + 0 + + id + consoleid + + + publicip + + + zoneid + + + zonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + + + privatemacaddress + + + created + + + hostid + + + state + Running + + + activeviewersessions + 0 + + + + + + + rebootSystemVm + Reboot system vm + + + id + consoleid + + + + + + + stopSystemVm + Stop system vm + + + id + consoleid + + + + + + + startSystemVm + Start system vm + + + id + consoleid + + + + + + + + deleteIso + Delete iso + + + id + privateisoid + + + + + + listIsos + List isos + true + + + id + privateisoid + + + + + + + deleteTemplate + Delete template + + + id + systemtemplateid + + + + + + listTemplates + List templates + true + + + id + systemtemplateid + + + templatefilter + self + + + + + + + deleteAccount + Deleting account + + + id + accountid + + + + + + diff --git a/test/metadata/func/regression_new.xml b/test/metadata/func/regression_new.xml index 3554e6e45a36..4987bfebee40 100644 --- a/test/metadata/func/regression_new.xml +++ b/test/metadata/func/regression_new.xml @@ -1,7672 +1,7672 @@ - - - - - - - - - - createUser - 3 [Create User Command] - - - username - usernameparam - - - password - password - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - accounttype - 0 - - - - - id - userid - - - username - usernameparam - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - account - accountname - - - - account - usernameparam - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - created - createtimestamp - - - state - enabled - - - - - - - listAccounts - 950 [List the Account of the User created in (3)] - - - name - accountname - - - - - id - accountid - - - name - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - vmavailable - Unlimited - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmstopped - 0 - - - vmrunning - 0 - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - state - enabled - - - - - - - updateUser - 6 [Update the User created in (3)] - - - id - userid - - - username - newuser - - - password - password - - - firstname - updatedadmin - - - lastname - updatedadminlastname - - - email - updatedadmin@gmail.com - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - id - userid - - - username - newuser - - - firstname - updatedadmin - - - lastname - updatedadminlastname - - - email - updatedadmin@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - listUsers - 5 [List the Updated User updated in (6)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatedadmin - - - lastname - updatedadminlastname - - - email - updatedadmin@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - state - enabled - - - created - createtimestamp - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - lockUser - [Lock the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatedadmin - - - lastname - updatedadminlastname - - - email - updatedadmin@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - state - locked - - - created - createtimestamp - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - disableUser - 428 [Disable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatedadmin - - - lastname - updatedadminlastname - - - email - updatedadmin@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - state - disabled - - - created - createtimestamp - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - enableUser - 9 [Enable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatedadmin - - - lastname - updatedadminlastname - - - email - updatedadmin@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - state - enabled - - - created - createtimestamp - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - createDomain - 10 [Create Domain] - - - name - domainname - - - - - name - domainname - - - id - newdomainid - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - listDomains - 11 [List Domains] - - - name - domainname - - - - - id - newdomainid - - - name - domainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - updateDomain - 952 [Update the Domain created in (10)] - - - id - newdomainid - - - name - newdomainname - - - - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - listDomains - - Check the Existence of the Updated Domain - - false - - - name - newdomainname - - - - - - - deleteDomain - 654 [Delete Domain created in (10)] - - - id - newdomainid - - - cleanup - true - - - - - success - true - - - - - - - listTemplates - 27 [List Templates - List System Template] - - - templatefilter - self - - - id - 1 - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - passwordenabled - debian50templatepasswordenabled - - - format - VHD - - - bootable - false - - - isfeatured - false - - - crossZones - true - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - size - debian50templatesize - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - domain - ROOT - - - domainid - 1 - - - - - - listTemplates - - Verify All the Parameters of the Listed Template - - - - name - debian50templatename - - - templatefilter - self - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - isfeatured - false - - - passwordenabled - debian50templatepasswordenabled - - - crossZones - true - - - format - VHD - - - bootable - false - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - debian50templatesize - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - - - - - listServiceOfferings - 28 [List the Small Instance Service Offering] - - - id - globalserviceofferingid - - - - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - smallofferingusenetwork - - - - - - listServiceOfferings - 876 [Verify the First Service Offering] - - - name - smallofferingname - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - smallofferingusenetwork - - - - - - - - listZones - 39 [List the Global Zone with id-2] - - - zone - 0 - - id - globalzoneid - - - name - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - internaldns1 - internaldns1 - - - vlan - - - guestcidraddress - 10.1.1.0/24 - - - networktype - Advanced - - - - - - - deployVirtualMachine - 13 [Deploy Virtual Machine] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - Testcase13-VM - - - group - Testcase13 - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - ipaddress - vmipaddress - - - serviceofferingid - globalserviceofferingid - - - haenable - false - - - state - Running - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - Testcase13-VM - - - group - Testcase13 - - - domainid - 1 - - - domain - ROOT - - - account - accountname - - - hostname - - - hostid - - - networkgrouplist - - - - - - - sleep.sh - - ** Sleep for a minute - - - s - 60 - - - - - - - listVirtualMachines - 957 [List Virtual Machines Command] - false - - - name - vmname - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - ipaddress - vmipaddress - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - haenable - false - - - displayname - Testcase13-VM - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - networkgrouplist - - - rootdeviceid - 0 - - - rootdevicetype - NetworkFilesystem - - - - - - - - listRouters - 35 [List Routers] - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - router - 0 - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - hostid - - - hostname - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - associateIpAddress - 546 [Associate IP Address] - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip - - - allocated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - issourcenat - false - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - forvirtualnetwork - - - vlanname - - - vlanid - - - - - - - - - createLoadBalancerRule - 986 [Create Load Balancer Rule] - - - name - lbname - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - description - FirstLoadBalancingRuleCreated - - - - - id - lbid - - - name - lbname - - - description - FirstLoadBalancingRuleCreated - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - listLoadBalancerRules - 988 [List Load Balancer Rules] - false - - - account - accountname - - - id - lbid - - - - - - - assignToLoadBalancerRule - 989 [Assign VM the Load Balancer Rule] - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - - deleteLoadBalancerRule - 987 [Load Balancer Rule] - - - id - lbid - - - - - - listLoadBalancerRules - true - - - - createLoadBalancerRule - + Create a Second Load Balancer Rule + - - - name - lbname - - - description - SecondLoadBalancerRuleCreated - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - - - id - lbid - - - name - lbname - - - description - SecondLoadBalancerRuleCreated - - - publicip - sourcenatpublicip - - - publicport - publicport - - - privateport - privateport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - assignToLoadBalancerRule - + Assign the Second Load Balancer Rule to a VM + - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - - listLoadBalancerRuleInstances - 991 [List LoadBalancer Rule Instances] - false - - - id - lbid - - - - - id - vmid - - - name - vmname - - - displayname - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - removeFromLoadBalancerRule - 990 [Remove From Load Balancer Rule] - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - listLoadBalancerRuleInstances - true - - - id - lbid - - - - - - - - - - - - - - - - - - - - - - - - - - - - - listVirtualMachines - 15 [List Virtual Machines] - - - account - accountname - - - name - vmname - - - - - id - vmid - - - name - vmname - - - created - - - ipaddress - vmipaddress - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - haenable - false - - - displayname - Testcase13-VM - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - guestosid - rootostypeid - - - networkgrouplist - - - rootdeviceid - 0 - - - rootdevicetype - NetworkFilesystem - - - - - - - rebootVirtualMachine - 16 [Reboot Virtual Machine] - - - id - vmid - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - ipaddress - vmipaddress - - - serviceofferingid - globalserviceofferingid - - - haenable - false - - - state - Running - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - Testcase13-VM - - - group - Testcase13 - - - domainid - 1 - - - domain - ROOT - - - account - accountname - - - hostname - - - hostid - - - networkgrouplist - - - - - - - - stopVirtualMachine - 17 [Stop Virtual Machine] - - - id - vmid - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - ipaddress - vmipaddress - - - serviceofferingid - globalserviceofferingid - - - haenable - false - - - state - Stopped - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - Testcase13-VM - - - group - Testcase13 - - - domainid - 1 - - - domain - ROOT - - - account - accountname - - - networkgrouplist - - - - - - - - - - listVolumes - [List the ROOT Volume of VM created in (13)] - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - name - rootvolumename - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - ROOT - - - deviceid - - - virtualmachineid - vmid - - - vmname - vmname - - - vmdisplayname - - - vmstate - Stopped - - - size - - - created - - - attached - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - sourceid - - - sourcetype - Template - - - hypervisor - XenServer - - - storage - primary storage - - - serviceofferingid - globalserviceofferingid - - - serviceofferingname - - - serviceofferingdisplaytext - - - - - - - listVolumes - [List the DATA Volume of VM created in (13)] - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - name - datavolumename - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - DATADISK - - - deviceid - - - virtualmachineid - vmid - - - vmname - vmname - - - vmdisplayname - - - vmstate - Stopped - - - size - - - created - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - sourceid - - - sourcetype - DiskOffering - - - hypervisor - XenServer - - - diskofferingid - globaldiskofferingid - - - diskofferingname - - - diskofferingdisplaytext - - - storage - primary storage - - - attached - - - destroyed - false - - - - - - - detachVolume - [Detach the DATA Volume of VM created in (13)] - - - id - datavolumeid - - - - - - listVolumes - [Empty List after Detaching the Volume of VM created in (13)] - true - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - 1026 [Create Volume] - - - account - accountname - - - domainid - 1 - - - name - newseconddiskvolume - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid - - - name - newseconddiskvolume - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - DATADISK - - - size - - - created - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - hypervisor - None - - - sourceid - - - sourcetype - DiskOffering - - - diskofferingid - globaldiskofferingid - - - diskofferingname - - - diskofferingdisplaytext - - - storage - primary storage - - - destroyed - false - - - - - - - - attachVolume - [Attach The Created Volume in (1026) to the VM created in (13)] - - - id - datavolumeid - - - virtualmachineid - vmid - - - - - id - datavolumeid - - - name - newseconddiskvolume - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - DATADISK - - - deviceid - - - virtualmachineid - vmid - - - vmname - vmname - - - vmdisplayname - Testcase13-VM - - - vmstate - Stopped - - - size - - - created - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - hypervisor - XenServer - - - sourceid - - - sourcetype - DiskOffering - - - diskofferingid - globaldiskofferingid - - - diskofferingname - - - diskofferingdisplaytext - - - storage - primary storage - - - attached - - - destroyed - false - - - - - - listVolumes - - Check that Data Disk Volume list is not Empty - - false - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - [Create the Volume, Used for Deletion Test] - - - account - accountname - - - domainid - 1 - - - name - newthirddiskvolume - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid1 - - - - - - deleteVolume - - Delete the Created Volume - - - - id - datavolumeid1 - - - - - success - true - - - - - - listVolumes - - Check the Emptiness of the Volumes List - - true - - - id - datavolumeid1 - - - - - - - - - createSnapshot - [Create Snapshot from Data Volume created in (1026)] - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - snapshottype - MANUAL - - - volumeid - datavolumeid - - - volumename - newseconddiskvolume - - - volumetype - DATADISK - - - created - - - name - - - intervaltype - MANUAL - - - - - - - listSnapshots - 26 [List Snapshots] - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - snapshottype - MANUAL - - - volumeid - datavolumeid - - - volumename - newseconddiskvolume - - - volumetype - DATADISK - - - created - - - name - - - intervaltype - MANUAL - - - - - - - deleteSnapshot - 24 [Delete the Created Snapshot] - - - id - snapshotid1 - - - - - success - true - - - - - - listSnapshots - - Check the Emptiness of the Snapshots list - - true - - - volumeid - datavolumeid - - - - - - - createSnapshot - + Create Snapshot to assist Template creation + - - - volumeid - rootvolumeid - - - - - id - snapshotid1 - - - - - - createTemplate - 958 [Create Template from Snapshot] - - - snapshotid - snapshotid1 - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - rootostypeid - - - isfeatured - true - - - ispublic - true - - - - - id - privatetemplateid - - - name - privatetemplatename - - - displaytext - privatetemplatedisplaytext - - - ispublic - true - - - created - - - isready - true - - - passwordenabled - false - - - isfeatured - true - - - crossZones - false - - - ostypeid - rootostypeid - - - ostypename - - - account - - - zoneid - globalzoneid - - - zonename - globalzonename - - - domain - ROOT - - - domainid - 1 - - - - - - - - - - registerTemplate - [Register template] - - - name - privatetemplatename - - - displaytext - privatetemplatedisplay - - - url - http://nfs1.lab.vmops.com/templates/centos53-x86_64/latest/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2 - - - ostypeid - 12 - - - format - vhd - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - XenServer - - - - - id - privatetemplateid - - - name - privatetemplatename - - - displaytext - privatetemplatedisplay - - - ispublic - false - - - created - - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - accountid - accountid - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Processing - - - hypervisor - XenServer - - - domainid - 1 - - - isextractable - true - - - - - - - sleep.sh - - ** Sleep for five minutes - - - s - 300 - - - - - - - listTemplates - 27 [List the Registered Template] - - - name - privatetemplatename - - - templatefilter - self - - - - - id - privatetemplateid - - - name - privatetemplatename - - - displaytext - privatetemplatedisplay - - - ispublic - false - - - created - - - isready - true - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - - - hypervisor - XenServer - - - domainid - 1 - - - domain - ROOT - - - - - - - updateTemplate - [Update Template registered in (958)] - - - id - privatetemplateid - - - displaytext - UpdatedTemplate - - - name - TemplateUpdated - - - - - id - privatetemplateid - - - name - TemplateUpdated - - - displaytext - UpdatedTemplate - - - ispublic - false - - - created - - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - - - hypervisor - XenServer - - - domainid - 1 - - - domain - ROOT - - - - - - sleep.sh - - ** Sleep for four minutes - - - s - 240 - - - - - - listTemplates - [List updated template] - - - id - privatetemplateid - - - templatefilter - self - - - - - id - privatetemplateid - - - name - TemplateUpdated - - - displaytext - UpdatedTemplate - - - ispublic - false - - - created - - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - - - hypervisor - XenServer - - - domainid - 1 - - - domain - ROOT - - - - - - - listTemplatePermissions - [List Template Permissions command] - - - id - privatetemplateid - - - templatefilter - self - - - - - id - privatetemplateid - - - ispublic - false - - - domainid - 1 - - - - - - - updateTemplatePermissions - [Update Template Permissions] - - - id - privatetemplateid - - - ispublic - true - - - - - success - true - - - - - - listTemplates - [List Template with Updated Permission] - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - true - - - - - - - deleteTemplate - 959 [Delete Template created in (958)] - - - id - privatetemplateid - - - - - success - true - - - - - - listTemplates - - Check the Emptiness of the Template List - - true - - - name - privatetemplatename - - - templatefilter - self - - - - - - - - changeServiceForVirtualMachine - 20 [Change Service Offering for Virtual Machine] - - - id - vmid - - - serviceofferingid - mediumglobalserviceofferingid - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Stopped - - - haenable - false - - - groupid - - - group - - - zoneid - globalzoneid - - - zonename - globalzonename - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - true - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - - - - - - listVirtualMachines - - Check the change in the Service Offering - - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - - - - - - - - - listVirtualMachines - + Check the Stopped status of VM that needs to be started + - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - startVirtualMachine - 18 [Start the Stopped VM] - - - id - vmid - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Running - - - haenable - false - - - groupid - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - NetworkFilesystem - - - networkgrouplist - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Running - - - haenable - false - - - groupid - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - NetworkFilesystem - - - networkgrouplist - - - - - - - updateVirtualMachine - + HA Enable on Virtual Machine + - - - id - vmid - - - haenable - true - - - - - success - true - - - - - - listVirtualMachines - 21 [Verify the Update using List VMs] - - - account - accountname - - - name - vmname - - - - - id - vmid - - - haenable - true - - - - - - updateVirtualMachine - + HA Disable on Virtual Machine + - - - id - vmid - - - haenable - false - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Running - - - haenable - false - - - groupid - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - NetworkFilesystem - - - networkgrouplist - - - - - - listVirtualMachines - 21 [Verify the Update using List VMs] - - - account - accountname - - - name - vmname - - - - - id - vmid - - - haenable - false - - - - - - - listPublicIpAddresses - 547 [List Public IP Addresses] - - - account - accountname - - - domainid - 1 - - - - publicipaddress - 0 - - ipaddress - - - allocated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - issourcenat - true - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - forvirtualnetwork - true - - - vlanid - - - vlanname - - - isstaticnat - - - - - - - disassociateIpAddress - 548 [Disassociate IP Address] - - - ipaddress - nonsourcenatpublicip - - - account - accountname - - - - - success - true - - - - - - - rebootRouter - 36 [Reboot Router] - - - id - routerid - - - - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - - stopRouter - 37 [Stop Router] - - - id - routerid - - - - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - state - Stopped - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - startRouter - 38 [Start Router] - - - id - routerid - - - - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - - listEvents - 40 [List Events] - - - account - accountname - - - - event - 0 - - id - - - username - - - type - - - level - - - description - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - - - parentid - - - - - - - destroyVirtualMachine - 14 [Destroy Virtual Machine] - - - id - vmid - - - - - - listVirtualMachines - - Verify the Destroyed Status of the Virtual Machine - - - - id - vmid - - - - - name - vmname - - - state - Destroyed - - - - - - - recoverVirtualMachine - 22 [ Recover Virtual Machine ] - - - id - vmid - - - - - success - true - - - - - - listVirtualMachines - - Verify the Stopped Status of the Recovered VM - - - - account - accountname - - - name - vmname - - - - - id - vmid - - - state - Stopped - - - - - - - listConfigurations - 549 [List Configurations] - - configuration - 0 - - category - - - name - - - value - - - description - - - - - - - listConfigurations - 878 [List Configurations with category parameter] - - - category - advanced - - - - configuration - 0 - - category - Advanced - - - name - - - value - - - description - - - - - - - - updateConfiguration - 550 [Update Configuration] - - - name - update.wait - - - value - 35 - - - - - success - true - - - displaytext - Successfully updated configuration value. - - - - - - listConfigurations - - Verify the Updated Configuration - - - - name - update.wait - - - - - category - Advanced - - - name - update.wait - - - value - 35 - - - description - Time to wait before alerting on a updating agent - - - - - - - - listPods - 552 [List Pods] - - - id - 2 - - - name - POD1 - - - - - id - 2 - - - name - POD1 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - cidr - - - startip - - - endip - - - gateway - - - - - - - createUser - 3 [Create User] - - - username - usernamejack - - - password - jack12 - - - firstname - jack12 - - - lastname - jacklastname - - - email - jack@gmail.com - - - accounttype - 0 - - - - - id - accountid1 - - - username - usernamejack - - - firstname - jack12 - - - lastname - jacklastname - - - email - jack@gmail.com - - - created - - - state - enabled - - - account - accountname1 - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - - - - listAccounts - - List Accounts - - - - name - accountname1 - - - - - id - accountid1 - - - name - accountname1 - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - updateAccount - 951 [Update Account] - - - account - accountname1 - - - domainid - 1 - - - newname - updatedname - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - listAccounts - - Check the Presence of the Updated Account - false - - - name - updatedname - - - - - id - accountid1 - - - name - updatedname - - - - - - - lockAccount - [Lock Account] - - - account - updatedname - - - domainid - 1 - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - locked - - - - - - - disableAccount - 7 [Disable Account] - - - account - updatedname - - - domainid - 1 - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - disabled - - - - - - - - enableAccount - 8 [Enable Account] - - - account - updatedname - - - domainid - 1 - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - updateResourceLimit - 953 [Create using Update Resource Limit] - - - resourcetype - 1 - - - max - 300 - - - domainid - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 300 - - - - - - - listResourceLimits - true - 956 [Verify the creation using List Resource Limits] - - - domainid - 1 - - - resourcetype - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 300 - - - - - - - updateResourceLimit - 954 [Update Resource Limit] - - - domainid - 1 - - - resourcetype - 1 - - - max - 65 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 65 - - - - - - listResourceLimits - - Verify the update using List Resource Limits - - - - domainid - 1 - - - resourcetype - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 65 - - - - - - - - createDiskOffering - 932 [Create Disk Offering] - - - name - newdiskname - - - description - newdiskdesc - - - disksize - 1 - - - displaytext - volumedisplay - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - - listDiskOfferings - 934 [List the Created Disk Offering] - - - name - newdiskname - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - - updateDiskOffering - Update the Created Disk Offering - - - id - diskid - - - name - newdiskname - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - listDiskOfferings - - Verify the Update using List Disk Offering - - - - name - newdiskname - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - - - deleteDiskOffering - 933 [Delete Disk Offering] - - - id - diskid - - - - - success - true - - - - - - listDiskOfferings - - Verify the Deletion of the Disk Offering - - true - - - id - diskid - - - - - - - - deleteUser - 4 [Delete User] - - - id - userid - - - - - success - true - - - - - - - - listAlerts - 750 [List Alerts] - false - - - - - listCapacity - 974 [List Capacity] - - capacity - 0 - - type - - - zoneid - globalzoneid - - - zonename - globalzonename - - - podid - - - podname - - - capacityused - - - capacitytotal - - - percentused - - - - - - - listHosts - 975 [List Hosts] - - - type - Routing - - - - host - 0 - - id - - - name - - - state - - - disconnected - - - type - Routing - - - ipaddress - - - zoneid - globalzoneid - - - zonename - globalzonename - - - podid - - - podname - - - version - - - hypervisor - XenServer - - - cpunumber - - - cpuspeed - - - cpuallocated - - - cpuused - - - averageload - - - networkkbsread - - - networkkbswrite - - - memorytotal - - - memoryallocated - - - memoryused - - - capabilities - - - lastpinged - - - managementserverid - - - clusterid - - - clustername - - - islocalstorageactive - - - created - - - events - - - - - - - - listIsos - 960 [List ISOs] - - iso - 0 - - id - - - name - xs-tools.iso - - - displaytext - xen-pv-drv-iso - - - ispublic - true - - - isready - true - - - created - - - bootable - false - - - isfeatured - true - - - crossZones - false - - - ostypeid - - - ostypename - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - isostatus - Successfully Installed - - - size - - - - - - - createServiceOffering - 962 [Create Service Offering] - - - name - servofferingname - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - listServiceOfferings - - - id - servofferingid - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - - updateServiceOffering - 964 [Update Service Offering] - - - id - servofferingid - - - name - servofferingname - - - displaytext - display - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - display - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - listServiceOfferings - - Verify the update of the Service Offering - - - - id - servofferingid - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - display - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - - - deleteServiceOffering - 963-1 [Delete Service Offering] - - - id - servofferingid - - - - - success - true - - - - - - listServiceOfferings - 963-2 [List Service Offering] - true - - - id - servofferingid - - - - - - - - registerIso - [Register ISO] - - - name - isoname - - - displaytext - isodisplay - - - url - http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso - - - ostypeid - 59 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - privateisoid - - - name - isoname - - - displaytext - isodisplay - - - ispublic - false - - - created - - - isready - false - - - isfeatured - false - - - bootable - true - - - ostypeid - 59 - - - ostypename - Other install media - - - accountid - 1 - - - account - system - - - domainid - 1 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - - - - sleep.sh - - ** Sleep for five minutes - - - s - 300 - - - - - - - listIsos - [List ISOs] - - - id - privateisoid - - - - - id - privateisoid - - - name - isoname - - - displaytext - isodisplay - - - ispublic - false - - - created - - - isready - true - - - bootable - true - - - isfeatured - false - - - crossZones - false - - - ostypeid - 59 - - - ostypename - Ubuntu - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - isostatus - Successfully Installed - - - size - - - - - - - registerTemplate - [Register Template] - - - name - registername - - - displaytext - registerdisplay - - - url - globaltemplateurl - - - ostypeid - 1 - - - format - vhd - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - XenServer - - - - - id - systemtemplateid - - - name - registername - - - displaytext - registerdisplay - - - ispublic - false - - - - created - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 1 - - - ostypename - CentOS 4.5 (32-bit) - - - accountid - 1 - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Processing - - - hypervisor - XenServer - - - domainid - 1 - - - isextractable - true - - - - - - - listSystemVms - [List system VM (Console Proxy VM)] - - - domainid - 1 - - - - systemvm - 0 - - id - consoleid - - - systemvmtype - consoleproxy - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Running - - - - - - - rebootSystemVm - [Reboot System VM] - - - id - consoleid - - - - - id - consoleid - - - systemvmtype - consoleproxy - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Running - - - - - - - stopSystemVm - [Stop System VM] - - - id - consoleid - - - - - id - consoleid - - - systemvmtype - consoleproxy - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - name - - - gateway - - - podid - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Stopped - - - - - - - startSystemVm - [Start System VM] - - - id - consoleid - - - - - id - consoleid - - - systemvmtype - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Running - - - - - - - deleteIso - [Delete ISO] - - - id - privateisoid - - - - - - listIsos - [List ISOs] - true - - - id - privateisoid - - - - - - - - deleteTemplate - [Delete template] - - - id - systemtemplateid - - - - - - listTemplates - [List templates] - true - - - id - systemtemplateid - - - templatefilter - self - - - - - + + + + + + + + + + createUser + 3 [Create User Command] + + + username + usernameparam + + + password + password + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + accounttype + 0 + + + + + id + userid + + + username + usernameparam + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + account + accountname + + + + account + usernameparam + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + created + createtimestamp + + + state + enabled + + + + + + + listAccounts + 950 [List the Account of the User created in (3)] + + + name + accountname + + + + + id + accountid + + + name + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + vmavailable + Unlimited + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmstopped + 0 + + + vmrunning + 0 + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + state + enabled + + + + + + + updateUser + 6 [Update the User created in (3)] + + + id + userid + + + username + newuser + + + password + password + + + firstname + updatedadmin + + + lastname + updatedadminlastname + + + email + updatedadmin@gmail.com + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + id + userid + + + username + newuser + + + firstname + updatedadmin + + + lastname + updatedadminlastname + + + email + updatedadmin@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + listUsers + 5 [List the Updated User updated in (6)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatedadmin + + + lastname + updatedadminlastname + + + email + updatedadmin@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + state + enabled + + + created + createtimestamp + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + lockUser + [Lock the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatedadmin + + + lastname + updatedadminlastname + + + email + updatedadmin@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + state + locked + + + created + createtimestamp + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + disableUser + 428 [Disable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatedadmin + + + lastname + updatedadminlastname + + + email + updatedadmin@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + state + disabled + + + created + createtimestamp + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + enableUser + 9 [Enable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatedadmin + + + lastname + updatedadminlastname + + + email + updatedadmin@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + state + enabled + + + created + createtimestamp + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + createDomain + 10 [Create Domain] + + + name + domainname + + + + + name + domainname + + + id + newdomainid + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + listDomains + 11 [List Domains] + + + name + domainname + + + + + id + newdomainid + + + name + domainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + updateDomain + 952 [Update the Domain created in (10)] + + + id + newdomainid + + + name + newdomainname + + + + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + listDomains + - Check the Existence of the Updated Domain - + false + + + name + newdomainname + + + + + + + deleteDomain + 654 [Delete Domain created in (10)] + + + id + newdomainid + + + cleanup + true + + + + + success + true + + + + + + + listTemplates + 27 [List Templates - List System Template] + + + templatefilter + self + + + id + 1 + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + passwordenabled + debian50templatepasswordenabled + + + format + VHD + + + bootable + false + + + isfeatured + false + + + crossZones + true + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + size + debian50templatesize + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + domain + ROOT + + + domainid + 1 + + + + + + listTemplates + - Verify All the Parameters of the Listed Template - + + + name + debian50templatename + + + templatefilter + self + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + isfeatured + false + + + passwordenabled + debian50templatepasswordenabled + + + crossZones + true + + + format + VHD + + + bootable + false + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + debian50templatesize + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + + + + + listServiceOfferings + 28 [List the Small Instance Service Offering] + + + id + globalserviceofferingid + + + + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + smallofferingusenetwork + + + + + + listServiceOfferings + 876 [Verify the First Service Offering] + + + name + smallofferingname + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + smallofferingusenetwork + + + + + + + + listZones + 39 [List the Global Zone with id-2] + + + zone + 0 + + id + globalzoneid + + + name + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + internaldns1 + internaldns1 + + + vlan + + + guestcidraddress + 10.1.1.0/24 + + + networktype + Advanced + + + + + + + deployVirtualMachine + 13 [Deploy Virtual Machine] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + Testcase13-VM + + + group + Testcase13 + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + ipaddress + vmipaddress + + + serviceofferingid + globalserviceofferingid + + + haenable + false + + + state + Running + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + Testcase13-VM + + + group + Testcase13 + + + domainid + 1 + + + domain + ROOT + + + account + accountname + + + hostname + + + hostid + + + networkgrouplist + + + + + + + sleep.sh + + ** Sleep for a minute + + + s + 60 + + + + + + + listVirtualMachines + 957 [List Virtual Machines Command] + false + + + name + vmname + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + ipaddress + vmipaddress + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + haenable + false + + + displayname + Testcase13-VM + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + networkgrouplist + + + rootdeviceid + 0 + + + rootdevicetype + NetworkFilesystem + + + + + + + + listRouters + 35 [List Routers] + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + router + 0 + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + hostid + + + hostname + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + associateIpAddress + 546 [Associate IP Address] + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip + + + allocated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + issourcenat + false + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + forvirtualnetwork + + + vlanname + + + vlanid + + + + + + + + + createLoadBalancerRule + 986 [Create Load Balancer Rule] + + + name + lbname + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + description + FirstLoadBalancingRuleCreated + + + + + id + lbid + + + name + lbname + + + description + FirstLoadBalancingRuleCreated + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + listLoadBalancerRules + 988 [List Load Balancer Rules] + false + + + account + accountname + + + id + lbid + + + + + + + assignToLoadBalancerRule + 989 [Assign VM the Load Balancer Rule] + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + + deleteLoadBalancerRule + 987 [Load Balancer Rule] + + + id + lbid + + + + + + listLoadBalancerRules + true + + + + createLoadBalancerRule + + Create a Second Load Balancer Rule + + + + name + lbname + + + description + SecondLoadBalancerRuleCreated + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + + + id + lbid + + + name + lbname + + + description + SecondLoadBalancerRuleCreated + + + publicip + sourcenatpublicip + + + publicport + publicport + + + privateport + privateport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + assignToLoadBalancerRule + + Assign the Second Load Balancer Rule to a VM + + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + + listLoadBalancerRuleInstances + 991 [List LoadBalancer Rule Instances] + false + + + id + lbid + + + + + id + vmid + + + name + vmname + + + displayname + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + removeFromLoadBalancerRule + 990 [Remove From Load Balancer Rule] + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + listLoadBalancerRuleInstances + true + + + id + lbid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + listVirtualMachines + 15 [List Virtual Machines] + + + account + accountname + + + name + vmname + + + + + id + vmid + + + name + vmname + + + created + + + ipaddress + vmipaddress + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + haenable + false + + + displayname + Testcase13-VM + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + guestosid + rootostypeid + + + networkgrouplist + + + rootdeviceid + 0 + + + rootdevicetype + NetworkFilesystem + + + + + + + rebootVirtualMachine + 16 [Reboot Virtual Machine] + + + id + vmid + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + ipaddress + vmipaddress + + + serviceofferingid + globalserviceofferingid + + + haenable + false + + + state + Running + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + Testcase13-VM + + + group + Testcase13 + + + domainid + 1 + + + domain + ROOT + + + account + accountname + + + hostname + + + hostid + + + networkgrouplist + + + + + + + + stopVirtualMachine + 17 [Stop Virtual Machine] + + + id + vmid + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + ipaddress + vmipaddress + + + serviceofferingid + globalserviceofferingid + + + haenable + false + + + state + Stopped + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + Testcase13-VM + + + group + Testcase13 + + + domainid + 1 + + + domain + ROOT + + + account + accountname + + + networkgrouplist + + + + + + + + + + listVolumes + [List the ROOT Volume of VM created in (13)] + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + name + rootvolumename + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + ROOT + + + deviceid + + + virtualmachineid + vmid + + + vmname + vmname + + + vmdisplayname + + + vmstate + Stopped + + + size + + + created + + + attached + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + sourceid + + + sourcetype + Template + + + hypervisor + XenServer + + + storage + primary storage + + + serviceofferingid + globalserviceofferingid + + + serviceofferingname + + + serviceofferingdisplaytext + + + + + + + listVolumes + [List the DATA Volume of VM created in (13)] + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + name + datavolumename + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + DATADISK + + + deviceid + + + virtualmachineid + vmid + + + vmname + vmname + + + vmdisplayname + + + vmstate + Stopped + + + size + + + created + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + sourceid + + + sourcetype + DiskOffering + + + hypervisor + XenServer + + + diskofferingid + globaldiskofferingid + + + diskofferingname + + + diskofferingdisplaytext + + + storage + primary storage + + + attached + + + destroyed + false + + + + + + + detachVolume + [Detach the DATA Volume of VM created in (13)] + + + id + datavolumeid + + + + + + listVolumes + [Empty List after Detaching the Volume of VM created in (13)] + true + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + 1026 [Create Volume] + + + account + accountname + + + domainid + 1 + + + name + newseconddiskvolume + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid + + + name + newseconddiskvolume + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + DATADISK + + + size + + + created + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + hypervisor + None + + + sourceid + + + sourcetype + DiskOffering + + + diskofferingid + globaldiskofferingid + + + diskofferingname + + + diskofferingdisplaytext + + + storage + primary storage + + + destroyed + false + + + + + + + + attachVolume + [Attach The Created Volume in (1026) to the VM created in (13)] + + + id + datavolumeid + + + virtualmachineid + vmid + + + + + id + datavolumeid + + + name + newseconddiskvolume + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + DATADISK + + + deviceid + + + virtualmachineid + vmid + + + vmname + vmname + + + vmdisplayname + Testcase13-VM + + + vmstate + Stopped + + + size + + + created + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + hypervisor + XenServer + + + sourceid + + + sourcetype + DiskOffering + + + diskofferingid + globaldiskofferingid + + + diskofferingname + + + diskofferingdisplaytext + + + storage + primary storage + + + attached + + + destroyed + false + + + + + + listVolumes + - Check that Data Disk Volume list is not Empty - + false + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + [Create the Volume, Used for Deletion Test] + + + account + accountname + + + domainid + 1 + + + name + newthirddiskvolume + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid1 + + + + + + deleteVolume + - Delete the Created Volume - + + + id + datavolumeid1 + + + + + success + true + + + + + + listVolumes + - Check the Emptiness of the Volumes List - + true + + + id + datavolumeid1 + + + + + + + + + createSnapshot + [Create Snapshot from Data Volume created in (1026)] + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + snapshottype + MANUAL + + + volumeid + datavolumeid + + + volumename + newseconddiskvolume + + + volumetype + DATADISK + + + created + + + name + + + intervaltype + MANUAL + + + + + + + listSnapshots + 26 [List Snapshots] + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + snapshottype + MANUAL + + + volumeid + datavolumeid + + + volumename + newseconddiskvolume + + + volumetype + DATADISK + + + created + + + name + + + intervaltype + MANUAL + + + + + + + deleteSnapshot + 24 [Delete the Created Snapshot] + + + id + snapshotid1 + + + + + success + true + + + + + + listSnapshots + - Check the Emptiness of the Snapshots list - + true + + + volumeid + datavolumeid + + + + + + + createSnapshot + + Create Snapshot to assist Template creation + + + + volumeid + rootvolumeid + + + + + id + snapshotid1 + + + + + + createTemplate + 958 [Create Template from Snapshot] + + + snapshotid + snapshotid1 + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + rootostypeid + + + isfeatured + true + + + ispublic + true + + + + + id + privatetemplateid + + + name + privatetemplatename + + + displaytext + privatetemplatedisplaytext + + + ispublic + true + + + created + + + isready + true + + + passwordenabled + false + + + isfeatured + true + + + crossZones + false + + + ostypeid + rootostypeid + + + ostypename + + + account + + + zoneid + globalzoneid + + + zonename + globalzonename + + + domain + ROOT + + + domainid + 1 + + + + + + + + + + registerTemplate + [Register template] + + + name + privatetemplatename + + + displaytext + privatetemplatedisplay + + + url + http://nfs1.lab.vmops.com/templates/centos53-x86_64/latest/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2 + + + ostypeid + 12 + + + format + vhd + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + XenServer + + + + + id + privatetemplateid + + + name + privatetemplatename + + + displaytext + privatetemplatedisplay + + + ispublic + false + + + created + + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + accountid + accountid + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Processing + + + hypervisor + XenServer + + + domainid + 1 + + + isextractable + true + + + + + + + sleep.sh + + ** Sleep for five minutes + + + s + 300 + + + + + + + listTemplates + 27 [List the Registered Template] + + + name + privatetemplatename + + + templatefilter + self + + + + + id + privatetemplateid + + + name + privatetemplatename + + + displaytext + privatetemplatedisplay + + + ispublic + false + + + created + + + isready + true + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + + + hypervisor + XenServer + + + domainid + 1 + + + domain + ROOT + + + + + + + updateTemplate + [Update Template registered in (958)] + + + id + privatetemplateid + + + displaytext + UpdatedTemplate + + + name + TemplateUpdated + + + + + id + privatetemplateid + + + name + TemplateUpdated + + + displaytext + UpdatedTemplate + + + ispublic + false + + + created + + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + + + hypervisor + XenServer + + + domainid + 1 + + + domain + ROOT + + + + + + sleep.sh + + ** Sleep for four minutes + + + s + 240 + + + + + + listTemplates + [List updated template] + + + id + privatetemplateid + + + templatefilter + self + + + + + id + privatetemplateid + + + name + TemplateUpdated + + + displaytext + UpdatedTemplate + + + ispublic + false + + + created + + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + + + hypervisor + XenServer + + + domainid + 1 + + + domain + ROOT + + + + + + + listTemplatePermissions + [List Template Permissions command] + + + id + privatetemplateid + + + templatefilter + self + + + + + id + privatetemplateid + + + ispublic + false + + + domainid + 1 + + + + + + + updateTemplatePermissions + [Update Template Permissions] + + + id + privatetemplateid + + + ispublic + true + + + + + success + true + + + + + + listTemplates + [List Template with Updated Permission] + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + true + + + + + + + deleteTemplate + 959 [Delete Template created in (958)] + + + id + privatetemplateid + + + + + success + true + + + + + + listTemplates + - Check the Emptiness of the Template List - + true + + + name + privatetemplatename + + + templatefilter + self + + + + + + + + changeServiceForVirtualMachine + 20 [Change Service Offering for Virtual Machine] + + + id + vmid + + + serviceofferingid + mediumglobalserviceofferingid + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Stopped + + + haenable + false + + + groupid + + + group + + + zoneid + globalzoneid + + + zonename + globalzonename + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + true + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + + + + + + listVirtualMachines + - Check the change in the Service Offering - + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + + + + + + + + + listVirtualMachines + + Check the Stopped status of VM that needs to be started + + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + startVirtualMachine + 18 [Start the Stopped VM] + + + id + vmid + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Running + + + haenable + false + + + groupid + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + NetworkFilesystem + + + networkgrouplist + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Running + + + haenable + false + + + groupid + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + NetworkFilesystem + + + networkgrouplist + + + + + + + updateVirtualMachine + + HA Enable on Virtual Machine + + + + id + vmid + + + haenable + true + + + + + success + true + + + + + + listVirtualMachines + 21 [Verify the Update using List VMs] + + + account + accountname + + + name + vmname + + + + + id + vmid + + + haenable + true + + + + + + updateVirtualMachine + + HA Disable on Virtual Machine + + + + id + vmid + + + haenable + false + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Running + + + haenable + false + + + groupid + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + NetworkFilesystem + + + networkgrouplist + + + + + + listVirtualMachines + 21 [Verify the Update using List VMs] + + + account + accountname + + + name + vmname + + + + + id + vmid + + + haenable + false + + + + + + + listPublicIpAddresses + 547 [List Public IP Addresses] + + + account + accountname + + + domainid + 1 + + + + publicipaddress + 0 + + ipaddress + + + allocated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + issourcenat + true + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + forvirtualnetwork + true + + + vlanid + + + vlanname + + + isstaticnat + + + + + + + disassociateIpAddress + 548 [Disassociate IP Address] + + + ipaddress + nonsourcenatpublicip + + + account + accountname + + + + + success + true + + + + + + + rebootRouter + 36 [Reboot Router] + + + id + routerid + + + + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + + stopRouter + 37 [Stop Router] + + + id + routerid + + + + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + state + Stopped + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + startRouter + 38 [Start Router] + + + id + routerid + + + + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + + listEvents + 40 [List Events] + + + account + accountname + + + + event + 0 + + id + + + username + + + type + + + level + + + description + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + + + parentid + + + + + + + destroyVirtualMachine + 14 [Destroy Virtual Machine] + + + id + vmid + + + + + + listVirtualMachines + - Verify the Destroyed Status of the Virtual Machine - + + + id + vmid + + + + + name + vmname + + + state + Destroyed + + + + + + + recoverVirtualMachine + 22 [ Recover Virtual Machine ] + + + id + vmid + + + + + success + true + + + + + + listVirtualMachines + - Verify the Stopped Status of the Recovered VM - + + + account + accountname + + + name + vmname + + + + + id + vmid + + + state + Stopped + + + + + + + listConfigurations + 549 [List Configurations] + + configuration + 0 + + category + + + name + + + value + + + description + + + + + + + listConfigurations + 878 [List Configurations with category parameter] + + + category + advanced + + + + configuration + 0 + + category + Advanced + + + name + + + value + + + description + + + + + + + + updateConfiguration + 550 [Update Configuration] + + + name + update.wait + + + value + 35 + + + + + success + true + + + displaytext + Successfully updated configuration value. + + + + + + listConfigurations + - Verify the Updated Configuration - + + + name + update.wait + + + + + category + Advanced + + + name + update.wait + + + value + 35 + + + description + Time to wait before alerting on a updating agent + + + + + + + + listPods + 552 [List Pods] + + + id + 2 + + + name + POD1 + + + + + id + 2 + + + name + POD1 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + cidr + + + startip + + + endip + + + gateway + + + + + + + createUser + 3 [Create User] + + + username + usernamejack + + + password + jack12 + + + firstname + jack12 + + + lastname + jacklastname + + + email + jack@gmail.com + + + accounttype + 0 + + + + + id + accountid1 + + + username + usernamejack + + + firstname + jack12 + + + lastname + jacklastname + + + email + jack@gmail.com + + + created + + + state + enabled + + + account + accountname1 + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + + + + listAccounts + - List Accounts - + + + name + accountname1 + + + + + id + accountid1 + + + name + accountname1 + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + updateAccount + 951 [Update Account] + + + account + accountname1 + + + domainid + 1 + + + newname + updatedname + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + listAccounts + - Check the Presence of the Updated Account + false + + + name + updatedname + + + + + id + accountid1 + + + name + updatedname + + + + + + + lockAccount + [Lock Account] + + + account + updatedname + + + domainid + 1 + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + locked + + + + + + + disableAccount + 7 [Disable Account] + + + account + updatedname + + + domainid + 1 + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + disabled + + + + + + + + enableAccount + 8 [Enable Account] + + + account + updatedname + + + domainid + 1 + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + updateResourceLimit + 953 [Create using Update Resource Limit] + + + resourcetype + 1 + + + max + 300 + + + domainid + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 300 + + + + + + + listResourceLimits + true + 956 [Verify the creation using List Resource Limits] + + + domainid + 1 + + + resourcetype + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 300 + + + + + + + updateResourceLimit + 954 [Update Resource Limit] + + + domainid + 1 + + + resourcetype + 1 + + + max + 65 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 65 + + + + + + listResourceLimits + - Verify the update using List Resource Limits - + + + domainid + 1 + + + resourcetype + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 65 + + + + + + + + createDiskOffering + 932 [Create Disk Offering] + + + name + newdiskname + + + description + newdiskdesc + + + disksize + 1 + + + displaytext + volumedisplay + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + + listDiskOfferings + 934 [List the Created Disk Offering] + + + name + newdiskname + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + + updateDiskOffering + Update the Created Disk Offering + + + id + diskid + + + name + newdiskname + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + listDiskOfferings + - Verify the Update using List Disk Offering - + + + name + newdiskname + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + + + deleteDiskOffering + 933 [Delete Disk Offering] + + + id + diskid + + + + + success + true + + + + + + listDiskOfferings + - Verify the Deletion of the Disk Offering - + true + + + id + diskid + + + + + + + + deleteUser + 4 [Delete User] + + + id + userid + + + + + success + true + + + + + + + + listAlerts + 750 [List Alerts] + false + + + + + listCapacity + 974 [List Capacity] + + capacity + 0 + + type + + + zoneid + globalzoneid + + + zonename + globalzonename + + + podid + + + podname + + + capacityused + + + capacitytotal + + + percentused + + + + + + + listHosts + 975 [List Hosts] + + + type + Routing + + + + host + 0 + + id + + + name + + + state + + + disconnected + + + type + Routing + + + ipaddress + + + zoneid + globalzoneid + + + zonename + globalzonename + + + podid + + + podname + + + version + + + hypervisor + XenServer + + + cpunumber + + + cpuspeed + + + cpuallocated + + + cpuused + + + averageload + + + networkkbsread + + + networkkbswrite + + + memorytotal + + + memoryallocated + + + memoryused + + + capabilities + + + lastpinged + + + managementserverid + + + clusterid + + + clustername + + + islocalstorageactive + + + created + + + events + + + + + + + + listIsos + 960 [List ISOs] + + iso + 0 + + id + + + name + xs-tools.iso + + + displaytext + xen-pv-drv-iso + + + ispublic + true + + + isready + true + + + created + + + bootable + false + + + isfeatured + true + + + crossZones + false + + + ostypeid + + + ostypename + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + isostatus + Successfully Installed + + + size + + + + + + + createServiceOffering + 962 [Create Service Offering] + + + name + servofferingname + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + listServiceOfferings + + + id + servofferingid + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + + updateServiceOffering + 964 [Update Service Offering] + + + id + servofferingid + + + name + servofferingname + + + displaytext + display + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + display + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + listServiceOfferings + - Verify the update of the Service Offering - + + + id + servofferingid + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + display + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + + + deleteServiceOffering + 963-1 [Delete Service Offering] + + + id + servofferingid + + + + + success + true + + + + + + listServiceOfferings + 963-2 [List Service Offering] + true + + + id + servofferingid + + + + + + + + registerIso + [Register ISO] + + + name + isoname + + + displaytext + isodisplay + + + url + http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso + + + ostypeid + 59 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + privateisoid + + + name + isoname + + + displaytext + isodisplay + + + ispublic + false + + + created + + + isready + false + + + isfeatured + false + + + bootable + true + + + ostypeid + 59 + + + ostypename + Other install media + + + accountid + 1 + + + account + system + + + domainid + 1 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + + + + sleep.sh + + ** Sleep for five minutes + + + s + 300 + + + + + + + listIsos + [List ISOs] + + + id + privateisoid + + + + + id + privateisoid + + + name + isoname + + + displaytext + isodisplay + + + ispublic + false + + + created + + + isready + true + + + bootable + true + + + isfeatured + false + + + crossZones + false + + + ostypeid + 59 + + + ostypename + Ubuntu + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + isostatus + Successfully Installed + + + size + + + + + + + registerTemplate + [Register Template] + + + name + registername + + + displaytext + registerdisplay + + + url + globaltemplateurl + + + ostypeid + 1 + + + format + vhd + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + XenServer + + + + + id + systemtemplateid + + + name + registername + + + displaytext + registerdisplay + + + ispublic + false + + + + created + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 1 + + + ostypename + CentOS 4.5 (32-bit) + + + accountid + 1 + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Processing + + + hypervisor + XenServer + + + domainid + 1 + + + isextractable + true + + + + + + + listSystemVms + [List system VM (Console Proxy VM)] + + + domainid + 1 + + + + systemvm + 0 + + id + consoleid + + + systemvmtype + consoleproxy + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Running + + + + + + + rebootSystemVm + [Reboot System VM] + + + id + consoleid + + + + + id + consoleid + + + systemvmtype + consoleproxy + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Running + + + + + + + stopSystemVm + [Stop System VM] + + + id + consoleid + + + + + id + consoleid + + + systemvmtype + consoleproxy + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + name + + + gateway + + + podid + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Stopped + + + + + + + startSystemVm + [Start System VM] + + + id + consoleid + + + + + id + consoleid + + + systemvmtype + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Running + + + + + + + deleteIso + [Delete ISO] + + + id + privateisoid + + + + + + listIsos + [List ISOs] + true + + + id + privateisoid + + + + + + + + deleteTemplate + [Delete template] + + + id + systemtemplateid + + + + + + listTemplates + [List templates] + true + + + id + systemtemplateid + + + templatefilter + self + + + + + diff --git a/test/metadata/func/regression_test.xml b/test/metadata/func/regression_test.xml index 53baea191b24..daac6385272b 100644 --- a/test/metadata/func/regression_test.xml +++ b/test/metadata/func/regression_test.xml @@ -1,3483 +1,3483 @@ - - - - - createUser - 3 - - - username - usernameparam - - - password - alena123 - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - accounttype - 0 - - - - - id - userid - - - username - usernameparam - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - created - - - state - enabled - - - - - - - listAccounts - 950 - - - name - accountname - - - - - id - accountid - - - name - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - ipavailable - - - iptotal - 0 - - - iplimit - - - vmavailable - - - vmlimit - - - vmtotal - 0 - - - vmstopped - 0 - - - vmrunning - 0 - - - volumelimit - - - volumetotal - 0 - - - volumeavailable - - - snapshotlimit - - - snapshottotal - 0 - - - snapshotavailable - - - templatelimit - - - templatetotal - 0 - - - templateavailable - - - state - enabled - - - iscleanuprequired - false - - - - - - - updateUser - 6 - - - id - userid - - - username - newuser - - - - - - - listUsers - 5 - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - state - enabled - - - created - - - - - - - lockUser - Lock user - - - id - userid - - - - - - - disableUser - 428 - - - id - userid - - - - - - - - - enableUser - 9 - - - id - userid - - - - - - - createDomain - 10 - - - name - domainname - - - - - name - domainname - - - id - newdomainid - - - - - - - - listDomains - 11 - - - name - domainname - - - - - id - newdomainid - - - name - domainname - - - - - - - updateDomain - 952 - - - id - newdomainid - - - name - newdomainname - - - - - - listDomains - false - - - name - newdomainname - - - - - - - deleteDomain - 654 - - - id - newdomainid - - - - - - - listTemplates - 27 - - - templatefilter - self - - - - template - 0 - - id - centos52templateid - - - name - centos52templatename - - - displaytext - centos52templatedisplaytext - - - ispublic - centos52templateispublic - - - created - centos52templatecreated - - - isready - true - - - passwordenabled - centos52templatepasswordenabled - - - - - - listTemplates - - - name - centos52templatename - - - templatefilter - self - - - - - id - centos52templateid - - - name - centos52templatename - - - displaytext - centos52templatedisplaytext - - - ispublic - centos52templateispublic - - - created - centos52templatecreated - - - isready - true - - - passwordenabled - centos52templatepasswordenabled - - - - - - - listServiceOfferings - 28 - - - id - globalserviceofferingid - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - - - - listServiceOfferings - 876 - - - id - globalserviceofferingid - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - displaytext - - - cpunumber - smallofferingcpunumber - 1 - - - cpuspeed - smallofferingcpuspeed - 500 - - - memory - smallofferingmemory - 512 - - - created - - - - - - - - listZones - 39 - - 0 - zone - - id - globalzoneid - - - name - ZONE1 - - - - - - - deployVirtualMachine - 13 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - 5 - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - namefordisplay - - - group - group1 - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - globalserviceofferingid - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - namefordisplay - - - group - group1 - - - - - - - - listVirtualMachines - 957 - false - - - name - vmname - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - namefordisplay - - - group - group1 - - - - - - - listRouters - 35 - - - zoneid - globalzoneid - - - account - system - - - domainid - 1 - - - - router - 0 - - id - routerid - - - publicip - sourcenatpublicip - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - routerpublicmacaddress - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - created - - - hostid - - - state - Running - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - listVirtualMachines - 15 - - - account - accountname - - - name - vmname - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - namefordisplay - - - group - group1 - - - - - - - - rebootVirtualMachine - 16 - - - id - vmid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Running - - - - - - - - stopVirtualMachine - 17 - - - id - vmid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - - - - - detachVolume - - - id - datavolumeid - - - - - - listVolumes - true - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - 1026 - - - account - accountname - - - domainid - 1 - - - name - - - zoneid - globalzoneid - - - diskofferingid - 5 - - - - - id - datavolumeid - - - - - - - - attachVolume - - - id - datavolumeid - - - virtualmachineid - vmid - - - - - - listVolumes - false - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - - - account - accountname - - - domainid - 1 - - - name - - - zoneid - globalzoneid - - - diskofferingid - 1 - - - - - id - datavolumeid1 - - - - - - deleteVolume - - - id - datavolumeid1 - - - - - - listVolumes - true - - - id - datavolumeid1 - - - - - - - - - createSnapshot - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - volumeid - datavolumeid - - - created - - - name - - - account - accountname - - - - - - listSnapshots - false - - - volumeid - datavolumeid - - - - - - - deleteSnapshot - 24 - - - id - snapshotid1 - - - - - - - listSnapshots - 26 - true - - - volumeid - datavolumeid - - - - - - - createTemplate - 958 - - - volumeid - datavolumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 1 - - - - - id - privatetemplateid - - - name - privatetemplatename - - - ispublic - false - - - isready - true - - - created - - - passwordenabled - false - - - - - - - listTemplates - 27 - - - name - privatetemplatename - - - templatefilter - self - - - - - id - privatetemplateid - - - name - privatetemplatename - - - ispublic - false - - - isready - true - - - created - - - passwordenabled - false - - - - - - - updateTemplate - Update template - - - id - privatetemplateid - - - displaytext - lalala - - - name - blah - - - - - - listTemplates - List updated template - - - id - privatetemplateid - - - templatefilter - self - - - - - displaytext - lalala - - - - - - - - listTemplatePermissions - list template permissions command - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - false - - - - - - - - updateTemplatePermissions - update template permissions - - - id - privatetemplateid - - - ispublic - true - - - - - - listTemplates - List template with updated permission - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - true - - - - - - - - deleteTemplate - 959 - - - id - privatetemplateid - - - - - - listTemplates - true - - - name - privatetemplatename - - - templatefilter - self - - - - - - - - - changeServiceForVirtualMachine - 20 - - - id - vmid - - - serviceofferingid - 2 - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - serviceofferingid - 2 - - - - - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - startVirtualMachine - 18 - - - id - vmid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - serviceofferingid - 2 - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Running - - - - - - - updateVirtualMachine - - - id - vmid - - - haenable - true - - - - - - listVirtualMachines - 21 - - - account - accountname - - - name - vmname - - - - - haenable - true - - - - - - updateVirtualMachine - - - id - vmid - - - haenable - false - - - - - - listVirtualMachines - 21 - - - account - accountname - - - name - vmname - - - - - haenable - false - - - - - - - - - - - - - - rebootRouter - 36 - - - id - routerid - - - - - - - stopRouter - 37 - - - id - routerid - - - - - - - startRouter - 38 - - - id - routerid - - - - - id - routerid - - - publicip - sourcenatpublicip - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - networkdomain - routernetworkdomain - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - routerpublicmacaddress - - - ipaddress - routeripaddress - - - macaddress - routermacaddress - - - created - - - hostid - - - state - Running - - - - - - - listEvents - 40 - - - account - accountname - - - - - - - destroyVirtualMachine - 14 - - - id - vmid - - - - - - listVirtualMachines - - - id - vmid - - - - - state - Destroyed - - - - - - - recoverVirtualMachine - 22 - - - id - vmid - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - - listConfigurations - 549 - false - - - - - listConfigurations - 878 - false - - - category - advanced - - - - - - - - updateConfiguration - 550 - - - name - update.wait - - - value - 35 - - - - - - listConfigurations - - - name - update.wait - - - - - value - 35 - - - - - - - - listPods - 552 - - - - - - - - - - createUser - 3 - - - username - - - password - jack12 - - - firstname - jack12 - - - lastname - jacklastname - - - email - jack@gmail.com - - - accounttype - 0 - - - - - account - accountname1 - - - - - - listAccounts - - - name - accountname1 - - - - - id - accountid1 - - - - - - updateAccount - 951 - - - account - accountname1 - - - domainid - 1 - - - newname - updatedname - - - - - - listAccounts - - - name - updatedname - - - - - id - accountid1 - - - name - updatedname - - - - - - - lockAccount - Lock Account - - - account - updatedname - - - domainid - 1 - - - - - - - disableAccount - 7 - - - account - updatedname - - - domainid - 1 - - - - - - - - enableAccount - 8 - - - account - updatedname - - - domainid - 1 - - - - - - - - updateResourceLimit - 953 - - - resourcetype - 1 - - - max - 300 - - - domainid - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 300 - - - - - - - listResourceLimits - true - 956 - - - domainid - 1 - - - resourcetype - 1 - - - - - max - 300 - - - - - - - updateResourceLimit - 954 - - - domainid - 1 - - - resourcetype - 1 - - - max - 65 - - - - - - listResourceLimits - - - domainid - 1 - - - resourcetype - 1 - - - - - max - 65 - - - - - - - - createDiskOffering - 932 - - - name - newdiskname - - - description - newdiskdesc - - - disksize - 1024 - - - displaytext - volumedisplay - - - - - id - diskid - - - domainid - 1 - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1099511627776 - - - ismirrored - false - - - - - - - - - listDiskOfferings - 934 - false - - - domainid - 1 - - - name - newdisk - - - - - - - - updateDiskOffering - - - id - diskid - - - name - newdiskname - - - - - - listDiskOfferings - - - domainid - 1 - - - name - newdiskname - - - - - name - newdiskname - - - - - - - - deleteDiskOffering - 933 - - - id - diskid - - - - - - listDiskOfferings - true - - - domainid - 1 - - - id - diskid - - - - - - - - deleteUser - 4 - - - id - userid - - - - - - - listAlerts - 750 - false - - - - - listCapacity - 974 - false - - - - - listHosts - false - 975 - - - - - - listIsos - 960 - true - - - - - createServiceOffering - 962 - - - name - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - - - id - soid - - - - - - listServiceOfferings - - - id - soid - - - - - id - newofferingid - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - - - - - - updateServiceOffering - 964 - - - id - newofferingid - - - name - - - displaytext - display - - - - - - - listServiceOfferings - - - id - newofferingid - - - - - id - newofferingid - - - displaytext - display - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - - - - - - deleteServiceOffering - 963-1 - - - id - newofferingid - - - - - - listServiceOfferings - 963-2 - true - - - id - newofferingid - - - - - - - - registerIso - Register iso - - - name - isoname - - - displaytext - isodisplay - - - url - http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso - - - ostypeid - 53 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - privateisoid - - - created - - - ispublic - false - - - bootable - true - - - isready - false - - - isfeatured - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - listIsos - List isos - false - - - id - privateisoid - - - - - - - registerTemplate - Register template - - - name - registername - - - displaytext - registerdisplay - - - url - globaltemplateurl - - - ostypeid - 1 - - - format - vhd - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - systemtemplateid - - - created - - - ispublic - false - - - isfeatured - false - - - passwordenabled - false - - - format - VHD - - - isready - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - - listSystemVms - List system vms - - - domainid - 1 - - - - systemvm - 0 - - id - consoleid - - - publicip - - - zoneid - - - zonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - - - privatemacaddress - - - created - - - hostid - - - state - Running - - - activeviewersessions - 0 - - - - - - - rebootSystemVm - Reboot system vm - - - id - consoleid - - - - - - - stopSystemVm - Stop system vm - - - id - consoleid - - - - - - - startSystemVm - Start system vm - - - id - consoleid - - - - - - - - deleteIso - Delete iso - - - id - privateisoid - - - - - - listIsos - List isos - true - - - id - privateisoid - - - - - - - deleteTemplate - Delete template - - - id - systemtemplateid - - - - - - listTemplates - List templates - true - - - id - systemtemplateid - - - templatefilter - self - - - - - - + + + + + createUser + 3 + + + username + usernameparam + + + password + alena123 + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + accounttype + 0 + + + + + id + userid + + + username + usernameparam + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + created + + + state + enabled + + + + + + + listAccounts + 950 + + + name + accountname + + + + + id + accountid + + + name + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + ipavailable + + + iptotal + 0 + + + iplimit + + + vmavailable + + + vmlimit + + + vmtotal + 0 + + + vmstopped + 0 + + + vmrunning + 0 + + + volumelimit + + + volumetotal + 0 + + + volumeavailable + + + snapshotlimit + + + snapshottotal + 0 + + + snapshotavailable + + + templatelimit + + + templatetotal + 0 + + + templateavailable + + + state + enabled + + + iscleanuprequired + false + + + + + + + updateUser + 6 + + + id + userid + + + username + newuser + + + + + + + listUsers + 5 + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + state + enabled + + + created + + + + + + + lockUser + Lock user + + + id + userid + + + + + + + disableUser + 428 + + + id + userid + + + + + + + + + enableUser + 9 + + + id + userid + + + + + + + createDomain + 10 + + + name + domainname + + + + + name + domainname + + + id + newdomainid + + + + + + + + listDomains + 11 + + + name + domainname + + + + + id + newdomainid + + + name + domainname + + + + + + + updateDomain + 952 + + + id + newdomainid + + + name + newdomainname + + + + + + listDomains + false + + + name + newdomainname + + + + + + + deleteDomain + 654 + + + id + newdomainid + + + + + + + listTemplates + 27 + + + templatefilter + self + + + + template + 0 + + id + centos52templateid + + + name + centos52templatename + + + displaytext + centos52templatedisplaytext + + + ispublic + centos52templateispublic + + + created + centos52templatecreated + + + isready + true + + + passwordenabled + centos52templatepasswordenabled + + + + + + listTemplates + + + name + centos52templatename + + + templatefilter + self + + + + + id + centos52templateid + + + name + centos52templatename + + + displaytext + centos52templatedisplaytext + + + ispublic + centos52templateispublic + + + created + centos52templatecreated + + + isready + true + + + passwordenabled + centos52templatepasswordenabled + + + + + + + listServiceOfferings + 28 + + + id + globalserviceofferingid + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + + + + listServiceOfferings + 876 + + + id + globalserviceofferingid + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + displaytext + + + cpunumber + smallofferingcpunumber + 1 + + + cpuspeed + smallofferingcpuspeed + 500 + + + memory + smallofferingmemory + 512 + + + created + + + + + + + + listZones + 39 + + 0 + zone + + id + globalzoneid + + + name + ZONE1 + + + + + + + deployVirtualMachine + 13 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + 5 + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + namefordisplay + + + group + group1 + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + globalserviceofferingid + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + namefordisplay + + + group + group1 + + + + + + + + listVirtualMachines + 957 + false + + + name + vmname + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + namefordisplay + + + group + group1 + + + + + + + listRouters + 35 + + + zoneid + globalzoneid + + + account + system + + + domainid + 1 + + + + router + 0 + + id + routerid + + + publicip + sourcenatpublicip + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + routerpublicmacaddress + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + created + + + hostid + + + state + Running + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + listVirtualMachines + 15 + + + account + accountname + + + name + vmname + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + namefordisplay + + + group + group1 + + + + + + + + rebootVirtualMachine + 16 + + + id + vmid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Running + + + + + + + + stopVirtualMachine + 17 + + + id + vmid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + + + + + detachVolume + + + id + datavolumeid + + + + + + listVolumes + true + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + 1026 + + + account + accountname + + + domainid + 1 + + + name + + + zoneid + globalzoneid + + + diskofferingid + 5 + + + + + id + datavolumeid + + + + + + + + attachVolume + + + id + datavolumeid + + + virtualmachineid + vmid + + + + + + listVolumes + false + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + + + account + accountname + + + domainid + 1 + + + name + + + zoneid + globalzoneid + + + diskofferingid + 1 + + + + + id + datavolumeid1 + + + + + + deleteVolume + + + id + datavolumeid1 + + + + + + listVolumes + true + + + id + datavolumeid1 + + + + + + + + + createSnapshot + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + volumeid + datavolumeid + + + created + + + name + + + account + accountname + + + + + + listSnapshots + false + + + volumeid + datavolumeid + + + + + + + deleteSnapshot + 24 + + + id + snapshotid1 + + + + + + + listSnapshots + 26 + true + + + volumeid + datavolumeid + + + + + + + createTemplate + 958 + + + volumeid + datavolumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 1 + + + + + id + privatetemplateid + + + name + privatetemplatename + + + ispublic + false + + + isready + true + + + created + + + passwordenabled + false + + + + + + + listTemplates + 27 + + + name + privatetemplatename + + + templatefilter + self + + + + + id + privatetemplateid + + + name + privatetemplatename + + + ispublic + false + + + isready + true + + + created + + + passwordenabled + false + + + + + + + updateTemplate + Update template + + + id + privatetemplateid + + + displaytext + lalala + + + name + blah + + + + + + listTemplates + List updated template + + + id + privatetemplateid + + + templatefilter + self + + + + + displaytext + lalala + + + + + + + + listTemplatePermissions + list template permissions command + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + false + + + + + + + + updateTemplatePermissions + update template permissions + + + id + privatetemplateid + + + ispublic + true + + + + + + listTemplates + List template with updated permission + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + true + + + + + + + + deleteTemplate + 959 + + + id + privatetemplateid + + + + + + listTemplates + true + + + name + privatetemplatename + + + templatefilter + self + + + + + + + + + changeServiceForVirtualMachine + 20 + + + id + vmid + + + serviceofferingid + 2 + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + serviceofferingid + 2 + + + + + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + startVirtualMachine + 18 + + + id + vmid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + serviceofferingid + 2 + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Running + + + + + + + updateVirtualMachine + + + id + vmid + + + haenable + true + + + + + + listVirtualMachines + 21 + + + account + accountname + + + name + vmname + + + + + haenable + true + + + + + + updateVirtualMachine + + + id + vmid + + + haenable + false + + + + + + listVirtualMachines + 21 + + + account + accountname + + + name + vmname + + + + + haenable + false + + + + + + + + + + + + + + rebootRouter + 36 + + + id + routerid + + + + + + + stopRouter + 37 + + + id + routerid + + + + + + + startRouter + 38 + + + id + routerid + + + + + id + routerid + + + publicip + sourcenatpublicip + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + networkdomain + routernetworkdomain + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + routerpublicmacaddress + + + ipaddress + routeripaddress + + + macaddress + routermacaddress + + + created + + + hostid + + + state + Running + + + + + + + listEvents + 40 + + + account + accountname + + + + + + + destroyVirtualMachine + 14 + + + id + vmid + + + + + + listVirtualMachines + + + id + vmid + + + + + state + Destroyed + + + + + + + recoverVirtualMachine + 22 + + + id + vmid + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + + listConfigurations + 549 + false + + + + + listConfigurations + 878 + false + + + category + advanced + + + + + + + + updateConfiguration + 550 + + + name + update.wait + + + value + 35 + + + + + + listConfigurations + + + name + update.wait + + + + + value + 35 + + + + + + + + listPods + 552 + + + + + + + + + + createUser + 3 + + + username + + + password + jack12 + + + firstname + jack12 + + + lastname + jacklastname + + + email + jack@gmail.com + + + accounttype + 0 + + + + + account + accountname1 + + + + + + listAccounts + + + name + accountname1 + + + + + id + accountid1 + + + + + + updateAccount + 951 + + + account + accountname1 + + + domainid + 1 + + + newname + updatedname + + + + + + listAccounts + + + name + updatedname + + + + + id + accountid1 + + + name + updatedname + + + + + + + lockAccount + Lock Account + + + account + updatedname + + + domainid + 1 + + + + + + + disableAccount + 7 + + + account + updatedname + + + domainid + 1 + + + + + + + + enableAccount + 8 + + + account + updatedname + + + domainid + 1 + + + + + + + + updateResourceLimit + 953 + + + resourcetype + 1 + + + max + 300 + + + domainid + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 300 + + + + + + + listResourceLimits + true + 956 + + + domainid + 1 + + + resourcetype + 1 + + + + + max + 300 + + + + + + + updateResourceLimit + 954 + + + domainid + 1 + + + resourcetype + 1 + + + max + 65 + + + + + + listResourceLimits + + + domainid + 1 + + + resourcetype + 1 + + + + + max + 65 + + + + + + + + createDiskOffering + 932 + + + name + newdiskname + + + description + newdiskdesc + + + disksize + 1024 + + + displaytext + volumedisplay + + + + + id + diskid + + + domainid + 1 + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1099511627776 + + + ismirrored + false + + + + + + + + + listDiskOfferings + 934 + false + + + domainid + 1 + + + name + newdisk + + + + + + + + updateDiskOffering + + + id + diskid + + + name + newdiskname + + + + + + listDiskOfferings + + + domainid + 1 + + + name + newdiskname + + + + + name + newdiskname + + + + + + + + deleteDiskOffering + 933 + + + id + diskid + + + + + + listDiskOfferings + true + + + domainid + 1 + + + id + diskid + + + + + + + + deleteUser + 4 + + + id + userid + + + + + + + listAlerts + 750 + false + + + + + listCapacity + 974 + false + + + + + listHosts + false + 975 + + + + + + listIsos + 960 + true + + + + + createServiceOffering + 962 + + + name + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + + + id + soid + + + + + + listServiceOfferings + + + id + soid + + + + + id + newofferingid + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + + + + + + updateServiceOffering + 964 + + + id + newofferingid + + + name + + + displaytext + display + + + + + + + listServiceOfferings + + + id + newofferingid + + + + + id + newofferingid + + + displaytext + display + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + + + + + + deleteServiceOffering + 963-1 + + + id + newofferingid + + + + + + listServiceOfferings + 963-2 + true + + + id + newofferingid + + + + + + + + registerIso + Register iso + + + name + isoname + + + displaytext + isodisplay + + + url + http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso + + + ostypeid + 53 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + privateisoid + + + created + + + ispublic + false + + + bootable + true + + + isready + false + + + isfeatured + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + listIsos + List isos + false + + + id + privateisoid + + + + + + + registerTemplate + Register template + + + name + registername + + + displaytext + registerdisplay + + + url + globaltemplateurl + + + ostypeid + 1 + + + format + vhd + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + systemtemplateid + + + created + + + ispublic + false + + + isfeatured + false + + + passwordenabled + false + + + format + VHD + + + isready + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + + listSystemVms + List system vms + + + domainid + 1 + + + + systemvm + 0 + + id + consoleid + + + publicip + + + zoneid + + + zonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + + + privatemacaddress + + + created + + + hostid + + + state + Running + + + activeviewersessions + 0 + + + + + + + rebootSystemVm + Reboot system vm + + + id + consoleid + + + + + + + stopSystemVm + Stop system vm + + + id + consoleid + + + + + + + startSystemVm + Start system vm + + + id + consoleid + + + + + + + + deleteIso + Delete iso + + + id + privateisoid + + + + + + listIsos + List isos + true + + + id + privateisoid + + + + + + + deleteTemplate + Delete template + + + id + systemtemplateid + + + + + + listTemplates + List templates + true + + + id + systemtemplateid + + + templatefilter + self + + + + + + diff --git a/test/metadata/func/regression_user.xml b/test/metadata/func/regression_user.xml index c58c848e9d5b..0f1e506f7bcc 100644 --- a/test/metadata/func/regression_user.xml +++ b/test/metadata/func/regression_user.xml @@ -1,2382 +1,2382 @@ - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - email - user@gmail.com - - - firstname - user - - - lastname - userlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmavailable - Unlimited - - - vmlimit - Unlimited - - - vmtotal - 0 - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - - id - userid - - - username - usernameparam - - - firstname - user - - - lastname - userlastname - - - email - user@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - - - - - - registerUserKeys - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - regression_user_network - - - displaytext - regression_user_network - - - - - id - networkid - - - - - - - listAccounts - true - 950 - - - id - accountid - - - name - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - ipavailable - - - iptotal - 0 - - - iplimit - - - vmavailable - - - vmlimit - - - vmtotal - 0 - - - vmstopped - 0 - - - vmrunning - 0 - - - volumelimit - - - volumetotal - 0 - - - volumeavailable - - - snapshotlimit - - - snapshottotal - 0 - - - snapshotavailable - - - templatelimit - - - templatetotal - 0 - - - templateavailable - - - - - - - - deployVirtualMachine - true - 13 - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - RegularUser-VM - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - account - accountname - - - templateid - globaltemplateid - - - displayname - RegularUser-VM - - - group - group1 - - - - - - - - listVirtualMachines - true - 957 - false - - - name - vmname - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - displayname - RegularUser-VM - - - group - group1 - - - - - - - listRouters - 35 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - router - 0 - - id - routerid - - - publicip - sourcenatpublicip - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicmacaddress - routerpublicmacaddress - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - created - - - hostid - - - state - Running - - - - - - - associateIpAddress - true - 546 - - - zoneid - globalzoneid - - - - - id - nonsourcenatpublicipid - - - ipaddress - nonsourcenatpublicip - - - allocated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - issourcenat - false - - - - - - - - - createLoadBalancerRule - true - 986 - - - name - lbname - - - publicipid - sourcenatpublicipid - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - - - id - lbid - - - name - lbname - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - publicip - sourcenatpublicip - - - account - accountname - - - - - - - - listLoadBalancerRules - true - 988 - false - - - - - - assignToLoadBalancerRule - true - 989 - - - id - lbid - - - virtualmachineid - vmid - - - - - - - deleteLoadBalancerRule - true - 987 - - - id - lbid - - - - - - listLoadBalancerRules - true - true - - - - createLoadBalancerRule - true - - - name - lbname - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - - - id - lbid - - - name - lbname - - - id - nonsourcenatpublicipid - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - publicip - sourcenatpublicip - - - account - accountname - - - - - - assignToLoadBalancerRule - true - - - id - lbid - - - virtualmachineid - vmid - - - - - - - - listLoadBalancerRuleInstances - true - 991 - false - - - id - lbid - - - - - - - removeFromLoadBalancerRule - true - 990 - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - listLoadBalancerRuleInstances - true - true - - - id - lbid - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - listVirtualMachines - true - 15 - - - name - vmname - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - displayname - namefordisplay - - - group - group1 - - - - - - - - rebootVirtualMachine - true - 16 - - - id - vmid - - - - - - listVirtualMachines - true - - - name - vmname - - - - - state - Running - - - - - - - - stopVirtualMachine - true - 17 - - - id - vmid - - - - - - listVirtualMachines - true - - - name - vmname - - - - - state - Stopped - - - - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - - - - - detachVolume - true - - - id - datavolumeid - - - - - - listVolumes - true - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - true - 1026 - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid - - - - - - - - attachVolume - true - - - id - datavolumeid - - - virtualmachineid - vmid - - - - - - listVolumes - true - false - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid1 - - - - - - deleteVolume - true - - - id - datavolumeid1 - - - - - - listVolumes - true - - - id - datavolumeid1 - - - - - - - - - createSnapshot - true - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - volumeid - datavolumeid - - - created - - - name - - - account - accountname - - - - - - listSnapshots - true - false - - - volumeid - datavolumeid - - - - - - - deleteSnapshot - true - 24 - - - id - snapshotid1 - - - - - - - listSnapshots - true - 26 - true - - - volumeid - datavolumeid - - - - - - - createSnapshot - true - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - - - - createTemplate - true - 958 - - - snapshotid - snapshotid1 - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 1 - - - - - id - privatetemplateid - - - name - privatetemplatename - - - ispublic - false - - - isready - true - - - created - - - passwordenabled - false - - - - - - - listTemplates - true - 27 - - - name - privatetemplatename - - - templatefilter - self - - - - - id - privatetemplateid - - - name - privatetemplatename - - - ispublic - false - - - isready - true - - - created - - - passwordenabled - false - - - - - - - updateTemplate - true - Update template - - - id - privatetemplateid - - - displaytext - blahblahblah - - - name - blah - - - - - - listTemplates - true - List updated template - - - id - privatetemplateid - - - templatefilter - self - - - - - displaytext - blahblahblah - - - - - - - - listTemplatePermissions - true - list template permissions command - - - id - privatetemplateid - - - - - ispublic - false - - - - - - - - updateTemplatePermissions - true - update template permissions - - - id - privatetemplateid - - - ispublic - true - - - - - - listTemplates - true - List template with updated permission - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - true - - - - - - - - deleteTemplate - true - 959 - - - id - privatetemplateid - - - - - - listTemplates - true - true - - - name - privatetemplatename - - - templatefilter - self - - - - - - stopVirtualMachine - true - Stop the VM before changing the Service - - - id - vmid - - - - - - - changeServiceForVirtualMachine - true - 20 - - - id - vmid - - - serviceofferingid - 2 - - - - - - listVirtualMachines - true - - - name - vmname - - - - - state - Stopped - - - serviceofferingid - 2 - - - - - - - - resetPasswordForVirtualMachine - true - 19 - - - id - vmid - - - - - id - vmid - - - password - - - - - - listVirtualMachines - true - - - name - vmname - - - - - state - Stopped - - - - - - - listVirtualMachines - true - - - name - vmname - - - - - state - Stopped - - - - - - startVirtualMachine - true - 18 - - - id - vmid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - created - - - zoneid - globalzoneid - - - zonename - globalzonename - - - haenable - false - - - state - Running - - - templateid - globaltemplateid - - - serviceofferingid - 2 - - - - - - listVirtualMachines - true - - - name - vmname - - - - - state - Running - - - - - - - updateVirtualMachine - true - - - id - vmid - - - haenable - true - - - - - - listVirtualMachines - true - 21 - - - name - vmname - - - - - haenable - true - - - - - - updateVirtualMachine - true - - - id - vmid - - - haenable - false - - - - - - listVirtualMachines - true - 21 - - - name - vmname - - - - - haenable - false - - - - - - - - - listPublicIpAddresses - true - 547 - - - - - - disassociateIpAddress - true - 548 - - - ipaddress - nonsourcenatpublicip - - - - - - - - listEvents - true - 40 - - - - - destroyVirtualMachine - true - 14 - - - id - vmid - - - - - - - - registerIso - true - Register iso - - - id - globalisoid - - - - - name - isoname - - - displaytext - isodisplay - - - url - http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso - - - ostypeid - 128 - - - zoneid - globalzoneid - - - - - id - privateisoid - - - created - - - ispublic - false - - - bootable - true - - - isready - false - - - isfeatured - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - listIsos - true - List isos - false - - - id - privateisoid - - - - - - - registerTemplate - true - Register template - - - name - registername - - - displaytext - registerdisplay - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - ostypeid - 132 - - - format - vhd - - - zoneid - globalzoneid - - - - - id - systemtemplateid - - - created - - - ispublic - false - - - isfeatured - false - - - passwordenabled - false - - - format - VHD - - - isready - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - - deleteIso - true - Delete iso - - - id - privateisoid - - - - - - listIsos - true - List isos - true - - - id - privateisoid - - - - - - - deleteTemplate - true - Delete template - - - id - systemtemplateid - - - - - - listTemplates - true - List templates - true - - - id - systemtemplateid - - - templatefilter - self - - - - - - + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + email + user@gmail.com + + + firstname + user + + + lastname + userlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmavailable + Unlimited + + + vmlimit + Unlimited + + + vmtotal + 0 + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + + id + userid + + + username + usernameparam + + + firstname + user + + + lastname + userlastname + + + email + user@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + + + + + + registerUserKeys + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + regression_user_network + + + displaytext + regression_user_network + + + + + id + networkid + + + + + + + listAccounts + true + 950 + + + id + accountid + + + name + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + ipavailable + + + iptotal + 0 + + + iplimit + + + vmavailable + + + vmlimit + + + vmtotal + 0 + + + vmstopped + 0 + + + vmrunning + 0 + + + volumelimit + + + volumetotal + 0 + + + volumeavailable + + + snapshotlimit + + + snapshottotal + 0 + + + snapshotavailable + + + templatelimit + + + templatetotal + 0 + + + templateavailable + + + + + + + + deployVirtualMachine + true + 13 + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + RegularUser-VM + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + account + accountname + + + templateid + globaltemplateid + + + displayname + RegularUser-VM + + + group + group1 + + + + + + + + listVirtualMachines + true + 957 + false + + + name + vmname + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + displayname + RegularUser-VM + + + group + group1 + + + + + + + listRouters + 35 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + router + 0 + + id + routerid + + + publicip + sourcenatpublicip + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicmacaddress + routerpublicmacaddress + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + created + + + hostid + + + state + Running + + + + + + + associateIpAddress + true + 546 + + + zoneid + globalzoneid + + + + + id + nonsourcenatpublicipid + + + ipaddress + nonsourcenatpublicip + + + allocated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + issourcenat + false + + + + + + + + + createLoadBalancerRule + true + 986 + + + name + lbname + + + publicipid + sourcenatpublicipid + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + + + id + lbid + + + name + lbname + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + publicip + sourcenatpublicip + + + account + accountname + + + + + + + + listLoadBalancerRules + true + 988 + false + + + + + + assignToLoadBalancerRule + true + 989 + + + id + lbid + + + virtualmachineid + vmid + + + + + + + deleteLoadBalancerRule + true + 987 + + + id + lbid + + + + + + listLoadBalancerRules + true + true + + + + createLoadBalancerRule + true + + + name + lbname + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + + + id + lbid + + + name + lbname + + + id + nonsourcenatpublicipid + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + publicip + sourcenatpublicip + + + account + accountname + + + + + + assignToLoadBalancerRule + true + + + id + lbid + + + virtualmachineid + vmid + + + + + + + + listLoadBalancerRuleInstances + true + 991 + false + + + id + lbid + + + + + + + removeFromLoadBalancerRule + true + 990 + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + listLoadBalancerRuleInstances + true + true + + + id + lbid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + listVirtualMachines + true + 15 + + + name + vmname + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + displayname + namefordisplay + + + group + group1 + + + + + + + + rebootVirtualMachine + true + 16 + + + id + vmid + + + + + + listVirtualMachines + true + + + name + vmname + + + + + state + Running + + + + + + + + stopVirtualMachine + true + 17 + + + id + vmid + + + + + + listVirtualMachines + true + + + name + vmname + + + + + state + Stopped + + + + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + + + + + detachVolume + true + + + id + datavolumeid + + + + + + listVolumes + true + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + true + 1026 + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid + + + + + + + + attachVolume + true + + + id + datavolumeid + + + virtualmachineid + vmid + + + + + + listVolumes + true + false + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid1 + + + + + + deleteVolume + true + + + id + datavolumeid1 + + + + + + listVolumes + true + + + id + datavolumeid1 + + + + + + + + + createSnapshot + true + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + volumeid + datavolumeid + + + created + + + name + + + account + accountname + + + + + + listSnapshots + true + false + + + volumeid + datavolumeid + + + + + + + deleteSnapshot + true + 24 + + + id + snapshotid1 + + + + + + + listSnapshots + true + 26 + true + + + volumeid + datavolumeid + + + + + + + createSnapshot + true + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + + + + createTemplate + true + 958 + + + snapshotid + snapshotid1 + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 1 + + + + + id + privatetemplateid + + + name + privatetemplatename + + + ispublic + false + + + isready + true + + + created + + + passwordenabled + false + + + + + + + listTemplates + true + 27 + + + name + privatetemplatename + + + templatefilter + self + + + + + id + privatetemplateid + + + name + privatetemplatename + + + ispublic + false + + + isready + true + + + created + + + passwordenabled + false + + + + + + + updateTemplate + true + Update template + + + id + privatetemplateid + + + displaytext + blahblahblah + + + name + blah + + + + + + listTemplates + true + List updated template + + + id + privatetemplateid + + + templatefilter + self + + + + + displaytext + blahblahblah + + + + + + + + listTemplatePermissions + true + list template permissions command + + + id + privatetemplateid + + + + + ispublic + false + + + + + + + + updateTemplatePermissions + true + update template permissions + + + id + privatetemplateid + + + ispublic + true + + + + + + listTemplates + true + List template with updated permission + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + true + + + + + + + + deleteTemplate + true + 959 + + + id + privatetemplateid + + + + + + listTemplates + true + true + + + name + privatetemplatename + + + templatefilter + self + + + + + + stopVirtualMachine + true + Stop the VM before changing the Service + + + id + vmid + + + + + + + changeServiceForVirtualMachine + true + 20 + + + id + vmid + + + serviceofferingid + 2 + + + + + + listVirtualMachines + true + + + name + vmname + + + + + state + Stopped + + + serviceofferingid + 2 + + + + + + + + resetPasswordForVirtualMachine + true + 19 + + + id + vmid + + + + + id + vmid + + + password + + + + + + listVirtualMachines + true + + + name + vmname + + + + + state + Stopped + + + + + + + listVirtualMachines + true + + + name + vmname + + + + + state + Stopped + + + + + + startVirtualMachine + true + 18 + + + id + vmid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + created + + + zoneid + globalzoneid + + + zonename + globalzonename + + + haenable + false + + + state + Running + + + templateid + globaltemplateid + + + serviceofferingid + 2 + + + + + + listVirtualMachines + true + + + name + vmname + + + + + state + Running + + + + + + + updateVirtualMachine + true + + + id + vmid + + + haenable + true + + + + + + listVirtualMachines + true + 21 + + + name + vmname + + + + + haenable + true + + + + + + updateVirtualMachine + true + + + id + vmid + + + haenable + false + + + + + + listVirtualMachines + true + 21 + + + name + vmname + + + + + haenable + false + + + + + + + + + listPublicIpAddresses + true + 547 + + + + + + disassociateIpAddress + true + 548 + + + ipaddress + nonsourcenatpublicip + + + + + + + + listEvents + true + 40 + + + + + destroyVirtualMachine + true + 14 + + + id + vmid + + + + + + + + registerIso + true + Register iso + + + id + globalisoid + + + + + name + isoname + + + displaytext + isodisplay + + + url + http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso + + + ostypeid + 128 + + + zoneid + globalzoneid + + + + + id + privateisoid + + + created + + + ispublic + false + + + bootable + true + + + isready + false + + + isfeatured + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + listIsos + true + List isos + false + + + id + privateisoid + + + + + + + registerTemplate + true + Register template + + + name + registername + + + displaytext + registerdisplay + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + ostypeid + 132 + + + format + vhd + + + zoneid + globalzoneid + + + + + id + systemtemplateid + + + created + + + ispublic + false + + + isfeatured + false + + + passwordenabled + false + + + format + VHD + + + isready + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + + deleteIso + true + Delete iso + + + id + privateisoid + + + + + + listIsos + true + List isos + true + + + id + privateisoid + + + + + + + deleteTemplate + true + Delete template + + + id + systemtemplateid + + + + + + listTemplates + true + List templates + true + + + id + systemtemplateid + + + templatefilter + self + + + + + + diff --git a/test/metadata/func/regression_works.xml b/test/metadata/func/regression_works.xml index 0b2f75438742..b9948d29b654 100644 --- a/test/metadata/func/regression_works.xml +++ b/test/metadata/func/regression_works.xml @@ -1,8418 +1,8418 @@ - - - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - admin - - - lastname - adminlastname - - - username - usernameparam - - - password - password - - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmavailable - Unlimited - - - vmlimit - Unlimited - - - vmtotal - 0 - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - - id - userid - - - username - usernameparam - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - - - - - - - - createUser - 3 [Create User Command] - - - account - accountname - - - accounttype - 1 - - - username - secondusernameparam - - - password - password - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - - - - id - seconduserid - - - username - usernameparam - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - created - createtimestamp - - - state - enabled - - - - - - - - - listAccounts - 950 [List the Account of the User created in (3)] - - - name - accountname - - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - updateUser - 6 [Update the User created in (3)] - - - id - userid - - - username - newuser - - - password - password - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - listUsers - 5 [List the Updated User updated in (6)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - - - - - - - disableUser - 428 [Disable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - disabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - enableUser - 9 [Enable the User created in (3)] - - - id - userid - - - - - id - userid - - - username - newuser - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - timezone - PST - - - apikey - userapikey - - - secretkey - usersecretkey - - - - - - - - - disableAccount - [Lock the Account] - - - account - accountname - - - domainid - 1 - - - lock - true - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - locked - - - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - 1 - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - disableAccount - [Lock the Account] - - - account - accountname - - - domainid - 1 - - - lock - false - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - disabled - - - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - 1 - - - - - id - accountid - - - name - accountname - - - accounttype - 1 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - - createDomain - 10 [Create Domain] - - - name - domainname - - - - - - name - domainname - - - id - newdomainid - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - listDomains - 11 [List Domains] - - - name - domainname - - - - - - id - newdomainid - - - name - domainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - updateDomain - 952 [Update the Domain created in (10)] - - - id - newdomainid - - - name - newdomainname - - - - - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - - listDomainChildren - - [List One of the Children of the Parent Domain] - - - id - 1 - - - - - domain - 0 - - id - newdomainid - - - name - newdomainname - - - level - 1 - - - parentdomainid - 1 - - - parentdomainname - ROOT - - - haschild - false - - - - - - - - - deleteDomain - 654 [Delete Domain created in (10)] - - - id - newdomainid - - - cleanup - true - - - - - - success - true - - - - - - - - - listTemplates - 27 [List Templates - List System Template] - - - templatefilter - self - - - id - 1 - - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - passwordenabled - debian50templatepasswordenabled - - - format - VHD - - - - isfeatured - false - - - crossZones - true - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - size - debian50templatesize - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - domain - ROOT - - - domainid - 1 - - - - - - - - listTemplates - - Verify All the Parameters of the Listed Template - - - - name - debian50templatename - - - templatefilter - self - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - isfeatured - false - - - passwordenabled - debian50templatepasswordenabled - - - crossZones - true - - - format - VHD - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - debian50templatesize - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - - - - - - - listServiceOfferings - 28 [List the Small Instance Service Offering] - - - id - globalserviceofferingid - - - - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - - - listServiceOfferings - 876 [Verify the First Service Offering] - - - name - smallofferingname - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - created - smallofferingcreatetime - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - - - - listZones - 39 [List the Global Zone with id-2] - - - zone - 0 - - id - globalzoneid - - - name - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - internaldns1 - internaldns1 - - - vlan - - - guestcidraddress - - - networktype - Advanced - - - - - - - - - deployVirtualMachine - 13 [Deploy Virtual Machine] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - Testcase13-VM - - - group - Testcase13 - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - ipaddress - vmipaddress - - - serviceofferingid - globalserviceofferingid - - - haenable - false - - - state - Running - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - Testcase13-VM - - - group - Testcase13 - - - domainid - 1 - - - domain - ROOT - - - account - accountname - - - hostname - - - hostid - - - networkgrouplist - - - - - - - sleep.sh - - ** Sleep for a minute - - - s - 60 - - - - - - - listVirtualMachines - 957 [List Virtual Machines Command] - false - - - name - vmname - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - ipaddress - vmipaddress - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - haenable - false - - - displayname - Testcase13-VM - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - networkgrouplist - - - rootdeviceid - 0 - - - rootdevicetype - NetworkFilesystem - - - - - - - - listRouters - 35 [List Routers] - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - router - 0 - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - hostid - - - hostname - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - associateIpAddress - 546 [Associate IP Address] - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - ipaddress - nonsourcenatpublicip - - - allocated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - issourcenat - false - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - forvirtualnetwork - - - vlanname - - - vlanid - - - - - - - - - createLoadBalancerRule - 986 [Create Load Balancer Rule] - - - name - lbname - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - description - FirstLoadBalancingRuleCreated - - - - - id - lbid - - - name - lbname - - - description - FirstLoadBalancingRuleCreated - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - listLoadBalancerRules - 988 [List Load Balancer Rules] - false - - - account - accountname - - - id - lbid - - - - - - - assignToLoadBalancerRule - 989 [Assign VM the Load Balancer Rule] - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - - deleteLoadBalancerRule - 987 [Load Balancer Rule] - - - id - lbid - - - - - - listLoadBalancerRules - true - - - - createLoadBalancerRule - + Create a Second Load Balancer Rule + - - - name - lbname - - - description - SecondLoadBalancerRuleCreated - - - publicip - sourcenatpublicip - - - privateport - privateport - - - publicport - publicport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - - - id - lbid - - - name - lbname - - - description - SecondLoadBalancerRuleCreated - - - publicip - sourcenatpublicip - - - publicport - publicport - - - privateport - privateport - - - algorithm - roundrobin - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - assignToLoadBalancerRule - + Assign the Second Load Balancer Rule to a VM + - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - - listLoadBalancerRuleInstances - 991 [List LoadBalancer Rule Instances] - false - - - id - lbid - - - - - id - vmid - - - name - vmname - - - displayname - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - removeFromLoadBalancerRule - 990 [Remove From Load Balancer Rule] - - - id - lbid - - - virtualmachineid - vmid - - - account - accountname - - - - - - listLoadBalancerRuleInstances - true - - - id - lbid - - - - - - - - - - - - - - - - - - - - - - - - - - - - - listVirtualMachines - 15 [List Virtual Machines] - - - account - accountname - - - name - vmname - - - - - id - vmid - - - name - vmname - - - created - - - ipaddress - vmipaddress - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - haenable - false - - - displayname - Testcase13-VM - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - smallofferingid - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - guestosid - rootostypeid - - - networkgrouplist - - - rootdeviceid - 0 - - - rootdevicetype - NetworkFilesystem - - - - - - - rebootVirtualMachine - 16 [Reboot Virtual Machine] - - - id - vmid - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - ipaddress - vmipaddress - - - serviceofferingid - globalserviceofferingid - - - haenable - false - - - state - Running - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - Testcase13-VM - - - group - Testcase13 - - - domainid - 1 - - - domain - ROOT - - - account - accountname - - - hostname - - - hostid - - - networkgrouplist - - - - - - - - stopVirtualMachine - 17 [Stop Virtual Machine] - - - id - vmid - - - - - id - vmid - - - name - vmname - - - created - vmcreated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - ipaddress - vmipaddress - - - serviceofferingid - globalserviceofferingid - - - haenable - false - - - state - Stopped - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingname - smallofferingname - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - displayname - Testcase13-VM - - - group - Testcase13 - - - domainid - 1 - - - domain - ROOT - - - account - accountname - - - networkgrouplist - - - - - - - - - - listVolumes - [List the ROOT Volume of VM created in (13)] - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - name - rootvolumename - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - ROOT - - - deviceid - - - virtualmachineid - vmid - - - vmname - vmname - - - vmdisplayname - - - vmstate - Stopped - - - size - - - created - - - attached - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - sourceid - - - sourcetype - Template - - - hypervisor - XenServer - - - storage - primary storage - - - serviceofferingid - globalserviceofferingid - - - serviceofferingname - - - serviceofferingdisplaytext - - - - - - - listVolumes - [List the DATA Volume of VM created in (13)] - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - name - datavolumename - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - DATADISK - - - deviceid - - - virtualmachineid - vmid - - - vmname - vmname - - - vmdisplayname - - - vmstate - Stopped - - - size - - - created - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - sourceid - - - sourcetype - DiskOffering - - - hypervisor - XenServer - - - diskofferingid - globaldiskofferingid - - - diskofferingname - - - diskofferingdisplaytext - - - storage - primary storage - - - attached - - - destroyed - false - - - - - - - detachVolume - [Detach the DATA Volume of VM created in (13)] - - - id - datavolumeid - - - - - - listVolumes - [Empty List after Detaching the Volume of VM created in (13)] - true - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - 1026 [Create Volume] - - - account - accountname - - - domainid - 1 - - - name - newseconddiskvolume - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid - - - name - newseconddiskvolume - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - DATADISK - - - size - - - created - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - hypervisor - None - - - sourceid - - - sourcetype - DiskOffering - - - diskofferingid - globaldiskofferingid - - - diskofferingname - - - diskofferingdisplaytext - - - storage - primary storage - - - destroyed - false - - - - - - - - attachVolume - [Attach The Created Volume in (1026) to the VM created in (13)] - - - id - datavolumeid - - - virtualmachineid - vmid - - - - - id - datavolumeid - - - name - newseconddiskvolume - - - zoneid - globalzoneid - - - zonename - globalzonename - - - type - DATADISK - - - deviceid - - - virtualmachineid - vmid - - - vmname - vmname - - - vmdisplayname - Testcase13-VM - - - vmstate - Stopped - - - size - - - created - - - state - Created - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - storagetype - shared - - - hypervisor - XenServer - - - sourceid - - - sourcetype - DiskOffering - - - diskofferingid - globaldiskofferingid - - - diskofferingname - - - diskofferingdisplaytext - - - storage - primary storage - - - attached - - - destroyed - false - - - - - - listVolumes - - Check that Data Disk Volume list is not Empty - - false - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - createVolume - [Create the Volume, Used for Deletion Test] - - - account - accountname - - - domainid - 1 - - - name - newthirddiskvolume - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - datavolumeid1 - - - - - - deleteVolume - - Delete the Created Volume - - - - id - datavolumeid1 - - - - - success - true - - - - - - listVolumes - - Check the Emptiness of the Volumes List - - true - - - id - datavolumeid1 - - - - - - - - - createSnapshot - [Create Snapshot from Data Volume created in (1026)] - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - snapshottype - MANUAL - - - volumeid - datavolumeid - - - volumename - newseconddiskvolume - - - volumetype - DATADISK - - - created - - - name - - - intervaltype - MANUAL - - - - - - - listSnapshots - 26 [List Snapshots] - - - volumeid - datavolumeid - - - - - id - snapshotid1 - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - snapshottype - MANUAL - - - volumeid - datavolumeid - - - volumename - newseconddiskvolume - - - volumetype - DATADISK - - - created - - - name - - - intervaltype - MANUAL - - - - - - - deleteSnapshot - 24 [Delete the Created Snapshot] - - - id - snapshotid1 - - - - - success - true - - - - - - listSnapshots - - Check the Emptiness of the Snapshots list - - true - - - volumeid - datavolumeid - - - - - - - createSnapshot - + Create Snapshot to assist Template creation + - - - volumeid - rootvolumeid - - - - - id - snapshotid1 - - - - - - createTemplate - 958 [Create Template from Snapshot] - - - snapshotid - snapshotid1 - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - rootostypeid - - - isfeatured - true - - - ispublic - true - - - - - id - privatetemplateid - - - name - privatetemplatename - - - displaytext - privatetemplatedisplaytext - - - ispublic - true - - - created - - - isready - true - - - passwordenabled - false - - - isfeatured - true - - - crossZones - false - - - ostypeid - rootostypeid - - - ostypename - - - account - - - zoneid - globalzoneid - - - zonename - globalzonename - - - domain - ROOT - - - domainid - 1 - - - - - - - - - - registerTemplate - [Register template] - - - name - privatetemplatename - - - displaytext - privatetemplatedisplay - - - url - http://nfs1.lab.vmops.com/templates/centos53-x86_64/latest/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2 - - - ostypeid - 12 - - - format - vhd - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - XenServer - - - - - id - privatetemplateid - - - name - privatetemplatename - - - displaytext - privatetemplatedisplay - - - ispublic - false - - - created - - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - accountid - accountid - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Processing - - - hypervisor - XenServer - - - domainid - 1 - - - isextractable - true - - - - - - - sleep.sh - - ** Sleep for five minutes - - - s - 300 - - - - - - - listTemplates - 27 [List the Registered Template] - - - name - privatetemplatename - - - templatefilter - self - - - - - id - privatetemplateid - - - name - privatetemplatename - - - displaytext - privatetemplatedisplay - - - ispublic - false - - - created - - - isready - true - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - - - hypervisor - XenServer - - - domainid - 1 - - - domain - ROOT - - - - - - - updateTemplate - [Update Template registered in (958)] - - - id - privatetemplateid - - - displaytext - UpdatedTemplate - - - name - TemplateUpdated - - - - - id - privatetemplateid - - - name - TemplateUpdated - - - displaytext - UpdatedTemplate - - - ispublic - false - - - created - - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - - - hypervisor - XenServer - - - domainid - 1 - - - domain - ROOT - - - - - - sleep.sh - - ** Sleep for four minutes - - - s - 240 - - - - - - listTemplates - [List updated template] - - - id - privatetemplateid - - - templatefilter - self - - - - - id - privatetemplateid - - - name - TemplateUpdated - - - displaytext - UpdatedTemplate - - - ispublic - false - - - created - - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 12 - - - ostypename - CentOS 5.3 (64-bit) - - - account - accountname - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - size - - - hypervisor - XenServer - - - domainid - 1 - - - domain - ROOT - - - - - - - listTemplatePermissions - [List Template Permissions command] - - - id - privatetemplateid - - - templatefilter - self - - - - - id - privatetemplateid - - - ispublic - false - - - domainid - 1 - - - - - - - updateTemplatePermissions - [Update Template Permissions] - - - id - privatetemplateid - - - ispublic - true - - - - - success - true - - - - - - listTemplates - [List Template with Updated Permission] - - - id - privatetemplateid - - - templatefilter - self - - - - - ispublic - true - - - - - - - deleteTemplate - 959 [Delete Template created in (958)] - - - id - privatetemplateid - - - - - success - true - - - - - - listTemplates - - Check the Emptiness of the Template List - - true - - - name - privatetemplatename - - - templatefilter - self - - - - - - - - changeServiceForVirtualMachine - 20 [Change Service Offering for Virtual Machine] - - - id - vmid - - - serviceofferingid - mediumglobalserviceofferingid - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Stopped - - - haenable - false - - - groupid - - - group - - - zoneid - globalzoneid - - - zonename - globalzonename - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - true - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - - - - - - listVirtualMachines - - Check the change in the Service Offering - - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - - - - - - - - - listVirtualMachines - + Check the Stopped status of VM that needs to be started + - - - account - accountname - - - name - vmname - - - - - state - Stopped - - - - - - startVirtualMachine - 18 [Start the Stopped VM] - - - id - vmid - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Running - - - haenable - false - - - groupid - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - NetworkFilesystem - - - networkgrouplist - - - - - - listVirtualMachines - - - account - accountname - - - name - vmname - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Running - - - haenable - false - - - groupid - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - NetworkFilesystem - - - networkgrouplist - - - - - - - updateVirtualMachine - + HA Enable on Virtual Machine + - - - id - vmid - - - haenable - true - - - - - success - true - - - - - - listVirtualMachines - 21 [Verify the Update using List VMs] - - - account - accountname - - - name - vmname - - - - - id - vmid - - - haenable - true - - - - - - updateVirtualMachine - + HA Disable on Virtual Machine + - - - id - vmid - - - haenable - false - - - - - id - vmid - - - name - vmname - - - displayname - Testcase13-VM - - - ipaddress - vmipaddress - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - Running - - - haenable - false - - - groupid - - - group - Testcase13 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - hostid - - - hostname - - - templateid - globaltemplateid - - - templatename - - - templatedisplaytext - - - passwordenabled - false - - - serviceofferingid - mediumglobalserviceofferingid - - - serviceofferingname - Medium Instance, Virtual Networking - - - forvirtualnetwork - - - cpunumber - 1 - - - cpuspeed - 1000 - - - memory - 1024 - - - cpuused - - - networkkbsread - - - networkkbswrite - - - guestosid - - - rootdeviceid - - - rootdevicetype - NetworkFilesystem - - - networkgrouplist - - - - - - listVirtualMachines - 21 [Verify the Update using List VMs] - - - account - accountname - - - name - vmname - - - - - id - vmid - - - haenable - false - - - - - - - listPublicIpAddresses - 547 [List Public IP Addresses] - - - account - accountname - - - domainid - 1 - - - - publicipaddress - 0 - - ipaddress - - - allocated - - - zoneid - globalzoneid - - - zonename - globalzonename - - - issourcenat - true - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - forvirtualnetwork - true - - - vlanid - - - vlanname - - - isstaticnat - - - - - - - disassociateIpAddress - 548 [Disassociate IP Address] - - - ipaddress - nonsourcenatpublicip - - - account - accountname - - - - - success - true - - - - - - - rebootRouter - 36 [Reboot Router] - - - id - routerid - - - - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - - stopRouter - 37 [Stop Router] - - - id - routerid - - - - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - state - Stopped - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - startRouter - 38 [Start Router] - - - id - routerid - - - - - id - routerid - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - routernetworkdomain - - - gateway - - - name - routername - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - sourcenatpublicip - - - publicmacaddress - routerpublicmacaddress - - - publicnetmask - - - guestipaddress - routeripaddress - - - guestmacaddress - routermacaddress - - - guestnetmask - - - templateid - - - created - - - state - Running - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - - - - - - listEvents - 40 [List Events] - - - account - accountname - - - - event - 0 - - id - - - username - - - type - - - level - - - description - - - account - accountname - - - domainid - 1 - - - domain - ROOT - - - created - - - state - - - parentid - - - - - - - destroyVirtualMachine - 14 [Destroy Virtual Machine] - - - id - vmid - - - - - - listVirtualMachines - - Verify the Destroyed Status of the Virtual Machine - - - - id - vmid - - - - - name - vmname - - - state - Destroyed - - - - - - - recoverVirtualMachine - 22 [ Recover Virtual Machine ] - - - id - vmid - - - - - success - true - - - - - - listVirtualMachines - - Verify the Stopped Status of the Recovered VM - - - - account - accountname - - - name - vmname - - - - - id - vmid - - - state - Stopped - - - - - - - listConfigurations - 549 [List Configurations] - - configuration - 0 - - category - - - name - - - value - - - description - - - - - - - listConfigurations - 878 [List Configurations with category parameter] - - - category - advanced - - - - configuration - 0 - - category - Advanced - - - name - - - value - - - description - - - - - - - - updateConfiguration - 550 [Update Configuration] - - - name - update.wait - - - value - 35 - - - - - success - true - - - displaytext - Successfully updated configuration value. - - - - - - listConfigurations - - Verify the Updated Configuration - - - - name - update.wait - - - - - category - Advanced - - - name - update.wait - - - value - 35 - - - description - Time to wait before alerting on a updating agent - - - - - - - - listPods - 552 [List Pods] - - - id - 2 - - - name - POD1 - - - - - id - 2 - - - name - POD1 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - cidr - - - startip - - - endip - - - gateway - - - - - - - createUser - 3 [Create User] - - - username - usernamejack - - - password - jack12 - - - firstname - jack12 - - - lastname - jacklastname - - - email - jack@gmail.com - - - accounttype - 0 - - - - - id - accountid1 - - - username - usernamejack - - - firstname - jack12 - - - lastname - jacklastname - - - email - jack@gmail.com - - - created - - - state - enabled - - - account - accountname1 - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - - - - listAccounts - - List Accounts - - - - name - accountname1 - - - - - id - accountid1 - - - name - accountname1 - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - updateAccount - 951 [Update Account] - - - account - accountname1 - - - domainid - 1 - - - newname - updatedname - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - listAccounts - - Check the Presence of the Updated Account - false - - - name - updatedname - - - - - id - accountid1 - - - name - updatedname - - - - - - - lockAccount - [Lock Account] - - - account - updatedname - - - domainid - 1 - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - locked - - - - - - - disableAccount - 7 [Disable Account] - - - account - updatedname - - - domainid - 1 - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - disabled - - - - - - - - enableAccount - 8 [Enable Account] - - - account - updatedname - - - domainid - 1 - - - - - id - accountid1 - - - name - updatedname - - - accounttype - 0 - - - domainid - 1 - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - iplimit - Unlimited - - - iptotal - 0 - - - ipavailable - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - updateResourceLimit - 953 [Create using Update Resource Limit] - - - resourcetype - 1 - - - max - 300 - - - domainid - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 300 - - - - - - - listResourceLimits - true - 956 [Verify the creation using List Resource Limits] - - - domainid - 1 - - - resourcetype - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 300 - - - - - - - updateResourceLimit - 954 [Update Resource Limit] - - - domainid - 1 - - - resourcetype - 1 - - - max - 65 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 65 - - - - - - listResourceLimits - - Verify the update using List Resource Limits - - - - domainid - 1 - - - resourcetype - 1 - - - - - domainid - 1 - - - domain - ROOT - - - resourcetype - 1 - - - max - 65 - - - - - - - - createDiskOffering - 932 [Create Disk Offering] - - - name - newdiskname - - - description - newdiskdesc - - - disksize - 1 - - - displaytext - volumedisplay - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - - listDiskOfferings - 934 [List the Created Disk Offering] - - - name - newdiskname - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - - updateDiskOffering - Update the Created Disk Offering - - - id - diskid - - - name - newdiskname - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - listDiskOfferings - - Verify the Update using List Disk Offering - - - - name - newdiskname - - - - - id - diskid - - - domainid - 1 - - - domain - ROOT - - - name - newdiskname - - - displaytext - volumedisplay - - - disksize - 1024 - - - created - - - isCustomized - false - - - - - - - - deleteDiskOffering - 933 [Delete Disk Offering] - - - id - diskid - - - - - success - true - - - - - - listDiskOfferings - - Verify the Deletion of the Disk Offering - - true - - - id - diskid - - - - - - - - deleteUser - 4 [Delete User] - - - id - userid - - - - - success - true - - - - - - - - listAlerts - 750 [List Alerts] - false - - - - - listCapacity - 974 [List Capacity] - - capacity - 0 - - type - - - zoneid - globalzoneid - - - zonename - globalzonename - - - podid - - - podname - - - capacityused - - - capacitytotal - - - percentused - - - - - - - listHosts - 975 [List Hosts] - - - type - Routing - - - - host - 0 - - id - - - name - - - state - - - disconnected - - - type - Routing - - - ipaddress - - - zoneid - globalzoneid - - - zonename - globalzonename - - - podid - - - podname - - - version - - - hypervisor - XenServer - - - cpunumber - - - cpuspeed - - - cpuallocated - - - cpuused - - - averageload - - - networkkbsread - - - networkkbswrite - - - memorytotal - - - memoryallocated - - - memoryused - - - capabilities - - - lastpinged - - - managementserverid - - - clusterid - - - clustername - - - islocalstorageactive - - - created - - - events - - - - - - - - listIsos - 960 [List ISOs] - - iso - 0 - - id - - - name - xs-tools.iso - - - displaytext - xen-pv-drv-iso - - - ispublic - true - - - isready - true - - - created - - - bootable - false - - - isfeatured - true - - - crossZones - false - - - ostypeid - - - ostypename - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - isostatus - Successfully Installed - - - size - - - - - - - createServiceOffering - 962 [Create Service Offering] - - - name - servofferingname - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - listServiceOfferings - - - id - servofferingid - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - newoff - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - - updateServiceOffering - 964 [Update Service Offering] - - - id - servofferingid - - - name - servofferingname - - - displaytext - display - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - display - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - listServiceOfferings - - Verify the update of the Service Offering - - - - id - servofferingid - - - - - id - servofferingid - - - name - servofferingname - - - displaytext - display - - - cpunumber - 1 - - - cpuspeed - 200 - - - memory - 256 - - - created - - - storagetype - shared - - - offerha - false - - - usevirtualnetwork - true - - - - - - - - deleteServiceOffering - 963-1 [Delete Service Offering] - - - id - servofferingid - - - - - success - true - - - - - - listServiceOfferings - 963-2 [List Service Offering] - true - - - id - servofferingid - - - - - - - - registerIso - [Register ISO] - - - name - isoname - - - displaytext - isodisplay - - - url - http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso - - - ostypeid - 59 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - privateisoid - - - name - isoname - - - displaytext - isodisplay - - - ispublic - false - - - created - - - isready - false - - - isfeatured - false - - - bootable - true - - - ostypeid - 59 - - - ostypename - Other install media - - - accountid - 1 - - - account - system - - - domainid - 1 - - - zoneid - globalzoneid - - - zonename - globalzonename - - - - - - sleep.sh - - ** Sleep for five minutes - - - s - 300 - - - - - - - listIsos - [List ISOs] - - - id - privateisoid - - - - - id - privateisoid - - - name - isoname - - - displaytext - isodisplay - - - ispublic - false - - - created - - - isready - true - - - bootable - true - - - isfeatured - false - - - crossZones - false - - - ostypeid - 59 - - - ostypename - Ubuntu - - - account - system - - - domainid - 1 - - - domain - ROOT - - - zoneid - globalzoneid - - - zonename - globalzonename - - - isostatus - Successfully Installed - - - size - - - - - - - registerTemplate - [Register Template] - - - name - registername - - - displaytext - registerdisplay - - - url - globaltemplateurl - - - ostypeid - 1 - - - format - vhd - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - XenServer - - - - - id - systemtemplateid - - - name - registername - - - displaytext - registerdisplay - - - ispublic - false - - - - created - - isready - false - - - passwordenabled - false - - - format - VHD - - - isfeatured - false - - - crossZones - false - - - ostypeid - 1 - - - ostypename - CentOS 4.5 (32-bit) - - - accountid - 1 - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Processing - - - hypervisor - XenServer - - - domainid - 1 - - - isextractable - true - - - - - - - listSystemVms - [List system VM (Console Proxy VM)] - - - domainid - 1 - - - - systemvm - 0 - - id - consoleid - - - systemvmtype - consoleproxy - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Running - - - - - - - rebootSystemVm - [Reboot System VM] - - - id - consoleid - - - - - id - consoleid - - - systemvmtype - consoleproxy - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Running - - - - - - - stopSystemVm - [Stop System VM] - - - id - consoleid - - - - - id - consoleid - - - systemvmtype - consoleproxy - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - name - - - gateway - - - podid - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Stopped - - - - - - - startSystemVm - [Start System VM] - - - id - consoleid - - - - - id - consoleid - - - systemvmtype - - - zoneid - globalzoneid - - - zonename - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - networkdomain - - - gateway - - - name - - - podid - - - hostid - - - hostname - - - privateip - - - privatemacaddress - - - privatenetmask - - - publicip - - - publicmacaddress - - - publicnetmask - - - templateid - 1 - - - created - - - activeviewersessions - 0 - - - state - Running - - - - - - - deleteIso - [Delete ISO] - - - id - privateisoid - - - - - - listIsos - [List ISOs] - true - - - id - privateisoid - - - - - - - - deleteTemplate - [Delete template] - - - id - systemtemplateid - - - - - - listTemplates - [List templates] - true - - - id - systemtemplateid - - - templatefilter - self - - - - - + + + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + admin + + + lastname + adminlastname + + + username + usernameparam + + + password + password + + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmavailable + Unlimited + + + vmlimit + Unlimited + + + vmtotal + 0 + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + + id + userid + + + username + usernameparam + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + + + + + + + + createUser + 3 [Create User Command] + + + account + accountname + + + accounttype + 1 + + + username + secondusernameparam + + + password + password + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + + + + id + seconduserid + + + username + usernameparam + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + created + createtimestamp + + + state + enabled + + + + + + + + + listAccounts + 950 [List the Account of the User created in (3)] + + + name + accountname + + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + updateUser + 6 [Update the User created in (3)] + + + id + userid + + + username + newuser + + + password + password + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + listUsers + 5 [List the Updated User updated in (6)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + + + + + + + disableUser + 428 [Disable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + disabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + enableUser + 9 [Enable the User created in (3)] + + + id + userid + + + + + id + userid + + + username + newuser + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + timezone + PST + + + apikey + userapikey + + + secretkey + usersecretkey + + + + + + + + + disableAccount + [Lock the Account] + + + account + accountname + + + domainid + 1 + + + lock + true + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + locked + + + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + 1 + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + disableAccount + [Lock the Account] + + + account + accountname + + + domainid + 1 + + + lock + false + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + disabled + + + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + 1 + + + + + id + accountid + + + name + accountname + + + accounttype + 1 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + + createDomain + 10 [Create Domain] + + + name + domainname + + + + + + name + domainname + + + id + newdomainid + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + listDomains + 11 [List Domains] + + + name + domainname + + + + + + id + newdomainid + + + name + domainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + updateDomain + 952 [Update the Domain created in (10)] + + + id + newdomainid + + + name + newdomainname + + + + + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + + listDomainChildren + - [List One of the Children of the Parent Domain] + + + id + 1 + + + + + domain + 0 + + id + newdomainid + + + name + newdomainname + + + level + 1 + + + parentdomainid + 1 + + + parentdomainname + ROOT + + + haschild + false + + + + + + + + + deleteDomain + 654 [Delete Domain created in (10)] + + + id + newdomainid + + + cleanup + true + + + + + + success + true + + + + + + + + + listTemplates + 27 [List Templates - List System Template] + + + templatefilter + self + + + id + 1 + + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + passwordenabled + debian50templatepasswordenabled + + + format + VHD + + + + isfeatured + false + + + crossZones + true + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + size + debian50templatesize + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + domain + ROOT + + + domainid + 1 + + + + + + + + listTemplates + - Verify All the Parameters of the Listed Template - + + + name + debian50templatename + + + templatefilter + self + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + isfeatured + false + + + passwordenabled + debian50templatepasswordenabled + + + crossZones + true + + + format + VHD + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + debian50templatesize + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + + + + + + + listServiceOfferings + 28 [List the Small Instance Service Offering] + + + id + globalserviceofferingid + + + + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + + + listServiceOfferings + 876 [Verify the First Service Offering] + + + name + smallofferingname + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + created + smallofferingcreatetime + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + + + + listZones + 39 [List the Global Zone with id-2] + + + zone + 0 + + id + globalzoneid + + + name + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + internaldns1 + internaldns1 + + + vlan + + + guestcidraddress + + + networktype + Advanced + + + + + + + + + deployVirtualMachine + 13 [Deploy Virtual Machine] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + Testcase13-VM + + + group + Testcase13 + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + ipaddress + vmipaddress + + + serviceofferingid + globalserviceofferingid + + + haenable + false + + + state + Running + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + Testcase13-VM + + + group + Testcase13 + + + domainid + 1 + + + domain + ROOT + + + account + accountname + + + hostname + + + hostid + + + networkgrouplist + + + + + + + sleep.sh + + ** Sleep for a minute + + + s + 60 + + + + + + + listVirtualMachines + 957 [List Virtual Machines Command] + false + + + name + vmname + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + ipaddress + vmipaddress + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + haenable + false + + + displayname + Testcase13-VM + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + networkgrouplist + + + rootdeviceid + 0 + + + rootdevicetype + NetworkFilesystem + + + + + + + + listRouters + 35 [List Routers] + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + router + 0 + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + hostid + + + hostname + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + associateIpAddress + 546 [Associate IP Address] + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + ipaddress + nonsourcenatpublicip + + + allocated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + issourcenat + false + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + forvirtualnetwork + + + vlanname + + + vlanid + + + + + + + + + createLoadBalancerRule + 986 [Create Load Balancer Rule] + + + name + lbname + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + description + FirstLoadBalancingRuleCreated + + + + + id + lbid + + + name + lbname + + + description + FirstLoadBalancingRuleCreated + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + listLoadBalancerRules + 988 [List Load Balancer Rules] + false + + + account + accountname + + + id + lbid + + + + + + + assignToLoadBalancerRule + 989 [Assign VM the Load Balancer Rule] + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + + deleteLoadBalancerRule + 987 [Load Balancer Rule] + + + id + lbid + + + + + + listLoadBalancerRules + true + + + + createLoadBalancerRule + + Create a Second Load Balancer Rule + + + + name + lbname + + + description + SecondLoadBalancerRuleCreated + + + publicip + sourcenatpublicip + + + privateport + privateport + + + publicport + publicport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + + + id + lbid + + + name + lbname + + + description + SecondLoadBalancerRuleCreated + + + publicip + sourcenatpublicip + + + publicport + publicport + + + privateport + privateport + + + algorithm + roundrobin + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + assignToLoadBalancerRule + + Assign the Second Load Balancer Rule to a VM + + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + + listLoadBalancerRuleInstances + 991 [List LoadBalancer Rule Instances] + false + + + id + lbid + + + + + id + vmid + + + name + vmname + + + displayname + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + removeFromLoadBalancerRule + 990 [Remove From Load Balancer Rule] + + + id + lbid + + + virtualmachineid + vmid + + + account + accountname + + + + + + listLoadBalancerRuleInstances + true + + + id + lbid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + listVirtualMachines + 15 [List Virtual Machines] + + + account + accountname + + + name + vmname + + + + + id + vmid + + + name + vmname + + + created + + + ipaddress + vmipaddress + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + haenable + false + + + displayname + Testcase13-VM + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + smallofferingid + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + guestosid + rootostypeid + + + networkgrouplist + + + rootdeviceid + 0 + + + rootdevicetype + NetworkFilesystem + + + + + + + rebootVirtualMachine + 16 [Reboot Virtual Machine] + + + id + vmid + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + ipaddress + vmipaddress + + + serviceofferingid + globalserviceofferingid + + + haenable + false + + + state + Running + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + Testcase13-VM + + + group + Testcase13 + + + domainid + 1 + + + domain + ROOT + + + account + accountname + + + hostname + + + hostid + + + networkgrouplist + + + + + + + + stopVirtualMachine + 17 [Stop Virtual Machine] + + + id + vmid + + + + + id + vmid + + + name + vmname + + + created + vmcreated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + ipaddress + vmipaddress + + + serviceofferingid + globalserviceofferingid + + + haenable + false + + + state + Stopped + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingname + smallofferingname + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + displayname + Testcase13-VM + + + group + Testcase13 + + + domainid + 1 + + + domain + ROOT + + + account + accountname + + + networkgrouplist + + + + + + + + + + listVolumes + [List the ROOT Volume of VM created in (13)] + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + name + rootvolumename + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + ROOT + + + deviceid + + + virtualmachineid + vmid + + + vmname + vmname + + + vmdisplayname + + + vmstate + Stopped + + + size + + + created + + + attached + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + sourceid + + + sourcetype + Template + + + hypervisor + XenServer + + + storage + primary storage + + + serviceofferingid + globalserviceofferingid + + + serviceofferingname + + + serviceofferingdisplaytext + + + + + + + listVolumes + [List the DATA Volume of VM created in (13)] + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + name + datavolumename + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + DATADISK + + + deviceid + + + virtualmachineid + vmid + + + vmname + vmname + + + vmdisplayname + + + vmstate + Stopped + + + size + + + created + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + sourceid + + + sourcetype + DiskOffering + + + hypervisor + XenServer + + + diskofferingid + globaldiskofferingid + + + diskofferingname + + + diskofferingdisplaytext + + + storage + primary storage + + + attached + + + destroyed + false + + + + + + + detachVolume + [Detach the DATA Volume of VM created in (13)] + + + id + datavolumeid + + + + + + listVolumes + [Empty List after Detaching the Volume of VM created in (13)] + true + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + 1026 [Create Volume] + + + account + accountname + + + domainid + 1 + + + name + newseconddiskvolume + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid + + + name + newseconddiskvolume + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + DATADISK + + + size + + + created + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + hypervisor + None + + + sourceid + + + sourcetype + DiskOffering + + + diskofferingid + globaldiskofferingid + + + diskofferingname + + + diskofferingdisplaytext + + + storage + primary storage + + + destroyed + false + + + + + + + + attachVolume + [Attach The Created Volume in (1026) to the VM created in (13)] + + + id + datavolumeid + + + virtualmachineid + vmid + + + + + id + datavolumeid + + + name + newseconddiskvolume + + + zoneid + globalzoneid + + + zonename + globalzonename + + + type + DATADISK + + + deviceid + + + virtualmachineid + vmid + + + vmname + vmname + + + vmdisplayname + Testcase13-VM + + + vmstate + Stopped + + + size + + + created + + + state + Created + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + storagetype + shared + + + hypervisor + XenServer + + + sourceid + + + sourcetype + DiskOffering + + + diskofferingid + globaldiskofferingid + + + diskofferingname + + + diskofferingdisplaytext + + + storage + primary storage + + + attached + + + destroyed + false + + + + + + listVolumes + - Check that Data Disk Volume list is not Empty - + false + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + createVolume + [Create the Volume, Used for Deletion Test] + + + account + accountname + + + domainid + 1 + + + name + newthirddiskvolume + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + datavolumeid1 + + + + + + deleteVolume + - Delete the Created Volume - + + + id + datavolumeid1 + + + + + success + true + + + + + + listVolumes + - Check the Emptiness of the Volumes List - + true + + + id + datavolumeid1 + + + + + + + + + createSnapshot + [Create Snapshot from Data Volume created in (1026)] + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + snapshottype + MANUAL + + + volumeid + datavolumeid + + + volumename + newseconddiskvolume + + + volumetype + DATADISK + + + created + + + name + + + intervaltype + MANUAL + + + + + + + listSnapshots + 26 [List Snapshots] + + + volumeid + datavolumeid + + + + + id + snapshotid1 + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + snapshottype + MANUAL + + + volumeid + datavolumeid + + + volumename + newseconddiskvolume + + + volumetype + DATADISK + + + created + + + name + + + intervaltype + MANUAL + + + + + + + deleteSnapshot + 24 [Delete the Created Snapshot] + + + id + snapshotid1 + + + + + success + true + + + + + + listSnapshots + - Check the Emptiness of the Snapshots list - + true + + + volumeid + datavolumeid + + + + + + + createSnapshot + + Create Snapshot to assist Template creation + + + + volumeid + rootvolumeid + + + + + id + snapshotid1 + + + + + + createTemplate + 958 [Create Template from Snapshot] + + + snapshotid + snapshotid1 + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + rootostypeid + + + isfeatured + true + + + ispublic + true + + + + + id + privatetemplateid + + + name + privatetemplatename + + + displaytext + privatetemplatedisplaytext + + + ispublic + true + + + created + + + isready + true + + + passwordenabled + false + + + isfeatured + true + + + crossZones + false + + + ostypeid + rootostypeid + + + ostypename + + + account + + + zoneid + globalzoneid + + + zonename + globalzonename + + + domain + ROOT + + + domainid + 1 + + + + + + + + + + registerTemplate + [Register template] + + + name + privatetemplatename + + + displaytext + privatetemplatedisplay + + + url + http://nfs1.lab.vmops.com/templates/centos53-x86_64/latest/f59f18fb-ae94-4f97-afd2-f84755767aca.vhd.bz2 + + + ostypeid + 12 + + + format + vhd + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + XenServer + + + + + id + privatetemplateid + + + name + privatetemplatename + + + displaytext + privatetemplatedisplay + + + ispublic + false + + + created + + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + accountid + accountid + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Processing + + + hypervisor + XenServer + + + domainid + 1 + + + isextractable + true + + + + + + + sleep.sh + + ** Sleep for five minutes + + + s + 300 + + + + + + + listTemplates + 27 [List the Registered Template] + + + name + privatetemplatename + + + templatefilter + self + + + + + id + privatetemplateid + + + name + privatetemplatename + + + displaytext + privatetemplatedisplay + + + ispublic + false + + + created + + + isready + true + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + + + hypervisor + XenServer + + + domainid + 1 + + + domain + ROOT + + + + + + + updateTemplate + [Update Template registered in (958)] + + + id + privatetemplateid + + + displaytext + UpdatedTemplate + + + name + TemplateUpdated + + + + + id + privatetemplateid + + + name + TemplateUpdated + + + displaytext + UpdatedTemplate + + + ispublic + false + + + created + + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + + + hypervisor + XenServer + + + domainid + 1 + + + domain + ROOT + + + + + + sleep.sh + + ** Sleep for four minutes + + + s + 240 + + + + + + listTemplates + [List updated template] + + + id + privatetemplateid + + + templatefilter + self + + + + + id + privatetemplateid + + + name + TemplateUpdated + + + displaytext + UpdatedTemplate + + + ispublic + false + + + created + + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 12 + + + ostypename + CentOS 5.3 (64-bit) + + + account + accountname + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + size + + + hypervisor + XenServer + + + domainid + 1 + + + domain + ROOT + + + + + + + listTemplatePermissions + [List Template Permissions command] + + + id + privatetemplateid + + + templatefilter + self + + + + + id + privatetemplateid + + + ispublic + false + + + domainid + 1 + + + + + + + updateTemplatePermissions + [Update Template Permissions] + + + id + privatetemplateid + + + ispublic + true + + + + + success + true + + + + + + listTemplates + [List Template with Updated Permission] + + + id + privatetemplateid + + + templatefilter + self + + + + + ispublic + true + + + + + + + deleteTemplate + 959 [Delete Template created in (958)] + + + id + privatetemplateid + + + + + success + true + + + + + + listTemplates + - Check the Emptiness of the Template List - + true + + + name + privatetemplatename + + + templatefilter + self + + + + + + + + changeServiceForVirtualMachine + 20 [Change Service Offering for Virtual Machine] + + + id + vmid + + + serviceofferingid + mediumglobalserviceofferingid + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Stopped + + + haenable + false + + + groupid + + + group + + + zoneid + globalzoneid + + + zonename + globalzonename + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + true + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + + + + + + listVirtualMachines + - Check the change in the Service Offering - + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + + + + + + + + + listVirtualMachines + + Check the Stopped status of VM that needs to be started + + + + account + accountname + + + name + vmname + + + + + state + Stopped + + + + + + startVirtualMachine + 18 [Start the Stopped VM] + + + id + vmid + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Running + + + haenable + false + + + groupid + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + NetworkFilesystem + + + networkgrouplist + + + + + + listVirtualMachines + + + account + accountname + + + name + vmname + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Running + + + haenable + false + + + groupid + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + NetworkFilesystem + + + networkgrouplist + + + + + + + updateVirtualMachine + + HA Enable on Virtual Machine + + + + id + vmid + + + haenable + true + + + + + success + true + + + + + + listVirtualMachines + 21 [Verify the Update using List VMs] + + + account + accountname + + + name + vmname + + + + + id + vmid + + + haenable + true + + + + + + updateVirtualMachine + + HA Disable on Virtual Machine + + + + id + vmid + + + haenable + false + + + + + id + vmid + + + name + vmname + + + displayname + Testcase13-VM + + + ipaddress + vmipaddress + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + Running + + + haenable + false + + + groupid + + + group + Testcase13 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + hostid + + + hostname + + + templateid + globaltemplateid + + + templatename + + + templatedisplaytext + + + passwordenabled + false + + + serviceofferingid + mediumglobalserviceofferingid + + + serviceofferingname + Medium Instance, Virtual Networking + + + forvirtualnetwork + + + cpunumber + 1 + + + cpuspeed + 1000 + + + memory + 1024 + + + cpuused + + + networkkbsread + + + networkkbswrite + + + guestosid + + + rootdeviceid + + + rootdevicetype + NetworkFilesystem + + + networkgrouplist + + + + + + listVirtualMachines + 21 [Verify the Update using List VMs] + + + account + accountname + + + name + vmname + + + + + id + vmid + + + haenable + false + + + + + + + listPublicIpAddresses + 547 [List Public IP Addresses] + + + account + accountname + + + domainid + 1 + + + + publicipaddress + 0 + + ipaddress + + + allocated + + + zoneid + globalzoneid + + + zonename + globalzonename + + + issourcenat + true + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + forvirtualnetwork + true + + + vlanid + + + vlanname + + + isstaticnat + + + + + + + disassociateIpAddress + 548 [Disassociate IP Address] + + + ipaddress + nonsourcenatpublicip + + + account + accountname + + + + + success + true + + + + + + + rebootRouter + 36 [Reboot Router] + + + id + routerid + + + + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + + stopRouter + 37 [Stop Router] + + + id + routerid + + + + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + state + Stopped + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + startRouter + 38 [Start Router] + + + id + routerid + + + + + id + routerid + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + routernetworkdomain + + + gateway + + + name + routername + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + sourcenatpublicip + + + publicmacaddress + routerpublicmacaddress + + + publicnetmask + + + guestipaddress + routeripaddress + + + guestmacaddress + routermacaddress + + + guestnetmask + + + templateid + + + created + + + state + Running + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + + + + + + listEvents + 40 [List Events] + + + account + accountname + + + + event + 0 + + id + + + username + + + type + + + level + + + description + + + account + accountname + + + domainid + 1 + + + domain + ROOT + + + created + + + state + + + parentid + + + + + + + destroyVirtualMachine + 14 [Destroy Virtual Machine] + + + id + vmid + + + + + + listVirtualMachines + - Verify the Destroyed Status of the Virtual Machine - + + + id + vmid + + + + + name + vmname + + + state + Destroyed + + + + + + + recoverVirtualMachine + 22 [ Recover Virtual Machine ] + + + id + vmid + + + + + success + true + + + + + + listVirtualMachines + - Verify the Stopped Status of the Recovered VM - + + + account + accountname + + + name + vmname + + + + + id + vmid + + + state + Stopped + + + + + + + listConfigurations + 549 [List Configurations] + + configuration + 0 + + category + + + name + + + value + + + description + + + + + + + listConfigurations + 878 [List Configurations with category parameter] + + + category + advanced + + + + configuration + 0 + + category + Advanced + + + name + + + value + + + description + + + + + + + + updateConfiguration + 550 [Update Configuration] + + + name + update.wait + + + value + 35 + + + + + success + true + + + displaytext + Successfully updated configuration value. + + + + + + listConfigurations + - Verify the Updated Configuration - + + + name + update.wait + + + + + category + Advanced + + + name + update.wait + + + value + 35 + + + description + Time to wait before alerting on a updating agent + + + + + + + + listPods + 552 [List Pods] + + + id + 2 + + + name + POD1 + + + + + id + 2 + + + name + POD1 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + cidr + + + startip + + + endip + + + gateway + + + + + + + createUser + 3 [Create User] + + + username + usernamejack + + + password + jack12 + + + firstname + jack12 + + + lastname + jacklastname + + + email + jack@gmail.com + + + accounttype + 0 + + + + + id + accountid1 + + + username + usernamejack + + + firstname + jack12 + + + lastname + jacklastname + + + email + jack@gmail.com + + + created + + + state + enabled + + + account + accountname1 + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + + + + listAccounts + - List Accounts - + + + name + accountname1 + + + + + id + accountid1 + + + name + accountname1 + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + updateAccount + 951 [Update Account] + + + account + accountname1 + + + domainid + 1 + + + newname + updatedname + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + listAccounts + - Check the Presence of the Updated Account + false + + + name + updatedname + + + + + id + accountid1 + + + name + updatedname + + + + + + + lockAccount + [Lock Account] + + + account + updatedname + + + domainid + 1 + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + locked + + + + + + + disableAccount + 7 [Disable Account] + + + account + updatedname + + + domainid + 1 + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + disabled + + + + + + + + enableAccount + 8 [Enable Account] + + + account + updatedname + + + domainid + 1 + + + + + id + accountid1 + + + name + updatedname + + + accounttype + 0 + + + domainid + 1 + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + iplimit + Unlimited + + + iptotal + 0 + + + ipavailable + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + updateResourceLimit + 953 [Create using Update Resource Limit] + + + resourcetype + 1 + + + max + 300 + + + domainid + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 300 + + + + + + + listResourceLimits + true + 956 [Verify the creation using List Resource Limits] + + + domainid + 1 + + + resourcetype + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 300 + + + + + + + updateResourceLimit + 954 [Update Resource Limit] + + + domainid + 1 + + + resourcetype + 1 + + + max + 65 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 65 + + + + + + listResourceLimits + - Verify the update using List Resource Limits - + + + domainid + 1 + + + resourcetype + 1 + + + + + domainid + 1 + + + domain + ROOT + + + resourcetype + 1 + + + max + 65 + + + + + + + + createDiskOffering + 932 [Create Disk Offering] + + + name + newdiskname + + + description + newdiskdesc + + + disksize + 1 + + + displaytext + volumedisplay + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + + listDiskOfferings + 934 [List the Created Disk Offering] + + + name + newdiskname + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + + updateDiskOffering + Update the Created Disk Offering + + + id + diskid + + + name + newdiskname + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + listDiskOfferings + - Verify the Update using List Disk Offering - + + + name + newdiskname + + + + + id + diskid + + + domainid + 1 + + + domain + ROOT + + + name + newdiskname + + + displaytext + volumedisplay + + + disksize + 1024 + + + created + + + isCustomized + false + + + + + + + + deleteDiskOffering + 933 [Delete Disk Offering] + + + id + diskid + + + + + success + true + + + + + + listDiskOfferings + - Verify the Deletion of the Disk Offering - + true + + + id + diskid + + + + + + + + deleteUser + 4 [Delete User] + + + id + userid + + + + + success + true + + + + + + + + listAlerts + 750 [List Alerts] + false + + + + + listCapacity + 974 [List Capacity] + + capacity + 0 + + type + + + zoneid + globalzoneid + + + zonename + globalzonename + + + podid + + + podname + + + capacityused + + + capacitytotal + + + percentused + + + + + + + listHosts + 975 [List Hosts] + + + type + Routing + + + + host + 0 + + id + + + name + + + state + + + disconnected + + + type + Routing + + + ipaddress + + + zoneid + globalzoneid + + + zonename + globalzonename + + + podid + + + podname + + + version + + + hypervisor + XenServer + + + cpunumber + + + cpuspeed + + + cpuallocated + + + cpuused + + + averageload + + + networkkbsread + + + networkkbswrite + + + memorytotal + + + memoryallocated + + + memoryused + + + capabilities + + + lastpinged + + + managementserverid + + + clusterid + + + clustername + + + islocalstorageactive + + + created + + + events + + + + + + + + listIsos + 960 [List ISOs] + + iso + 0 + + id + + + name + xs-tools.iso + + + displaytext + xen-pv-drv-iso + + + ispublic + true + + + isready + true + + + created + + + bootable + false + + + isfeatured + true + + + crossZones + false + + + ostypeid + + + ostypename + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + isostatus + Successfully Installed + + + size + + + + + + + createServiceOffering + 962 [Create Service Offering] + + + name + servofferingname + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + listServiceOfferings + + + id + servofferingid + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + newoff + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + + updateServiceOffering + 964 [Update Service Offering] + + + id + servofferingid + + + name + servofferingname + + + displaytext + display + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + display + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + listServiceOfferings + - Verify the update of the Service Offering - + + + id + servofferingid + + + + + id + servofferingid + + + name + servofferingname + + + displaytext + display + + + cpunumber + 1 + + + cpuspeed + 200 + + + memory + 256 + + + created + + + storagetype + shared + + + offerha + false + + + usevirtualnetwork + true + + + + + + + + deleteServiceOffering + 963-1 [Delete Service Offering] + + + id + servofferingid + + + + + success + true + + + + + + listServiceOfferings + 963-2 [List Service Offering] + true + + + id + servofferingid + + + + + + + + registerIso + [Register ISO] + + + name + isoname + + + displaytext + isodisplay + + + url + http://172.16.0.220/images/sumita/iso/ubuntu-9.04-server-amd64.iso + + + ostypeid + 59 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + privateisoid + + + name + isoname + + + displaytext + isodisplay + + + ispublic + false + + + created + + + isready + false + + + isfeatured + false + + + bootable + true + + + ostypeid + 59 + + + ostypename + Other install media + + + accountid + 1 + + + account + system + + + domainid + 1 + + + zoneid + globalzoneid + + + zonename + globalzonename + + + + + + sleep.sh + + ** Sleep for five minutes + + + s + 300 + + + + + + + listIsos + [List ISOs] + + + id + privateisoid + + + + + id + privateisoid + + + name + isoname + + + displaytext + isodisplay + + + ispublic + false + + + created + + + isready + true + + + bootable + true + + + isfeatured + false + + + crossZones + false + + + ostypeid + 59 + + + ostypename + Ubuntu + + + account + system + + + domainid + 1 + + + domain + ROOT + + + zoneid + globalzoneid + + + zonename + globalzonename + + + isostatus + Successfully Installed + + + size + + + + + + + registerTemplate + [Register Template] + + + name + registername + + + displaytext + registerdisplay + + + url + globaltemplateurl + + + ostypeid + 1 + + + format + vhd + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + XenServer + + + + + id + systemtemplateid + + + name + registername + + + displaytext + registerdisplay + + + ispublic + false + + + + created + + isready + false + + + passwordenabled + false + + + format + VHD + + + isfeatured + false + + + crossZones + false + + + ostypeid + 1 + + + ostypename + CentOS 4.5 (32-bit) + + + accountid + 1 + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Processing + + + hypervisor + XenServer + + + domainid + 1 + + + isextractable + true + + + + + + + listSystemVms + [List system VM (Console Proxy VM)] + + + domainid + 1 + + + + systemvm + 0 + + id + consoleid + + + systemvmtype + consoleproxy + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Running + + + + + + + rebootSystemVm + [Reboot System VM] + + + id + consoleid + + + + + id + consoleid + + + systemvmtype + consoleproxy + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Running + + + + + + + stopSystemVm + [Stop System VM] + + + id + consoleid + + + + + id + consoleid + + + systemvmtype + consoleproxy + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + name + + + gateway + + + podid + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Stopped + + + + + + + startSystemVm + [Start System VM] + + + id + consoleid + + + + + id + consoleid + + + systemvmtype + + + zoneid + globalzoneid + + + zonename + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + networkdomain + + + gateway + + + name + + + podid + + + hostid + + + hostname + + + privateip + + + privatemacaddress + + + privatenetmask + + + publicip + + + publicmacaddress + + + publicnetmask + + + templateid + 1 + + + created + + + activeviewersessions + 0 + + + state + Running + + + + + + + deleteIso + [Delete ISO] + + + id + privateisoid + + + + + + listIsos + [List ISOs] + true + + + id + privateisoid + + + + + + + + deleteTemplate + [Delete template] + + + id + systemtemplateid + + + + + + listTemplates + [List templates] + true + + + id + systemtemplateid + + + templatefilter + self + + + + + diff --git a/test/metadata/func/resource_limits.xml b/test/metadata/func/resource_limits.xml index 838ec93144a1..39b7f08a8e9c 100644 --- a/test/metadata/func/resource_limits.xml +++ b/test/metadata/func/resource_limits.xml @@ -1,1894 +1,1894 @@ - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - email - user@gmail.com - - - firstname - user - - - lastname - userlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountname - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - id - userid - - - - - - - - - - - updateResourceLimit - Setup VMs Limit for Account Test Case - - - resourcetype - 0 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 0 - - - max - 2 - - - - - - - updateResourceLimit - Setup Public-Ips Limit for Account Test Case - - - resourcetype - 1 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 1 - - - max - 1 - - - - - - - updateResourceLimit - Setup Volumes Limit for Account Test Case - - - resourcetype - 2 - - - max - 6 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 2 - - - max - 6 - - - - - - - updateResourceLimit - Setup Snapshots Limit for Account Test Case - - - resourcetype - 3 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 3 - - - max - 2 - - - - - - - updateResourceLimit - Setup Templates Limit for Account Test Case - - - resourcetype - 4 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 4 - - - max - 2 - - - - - - - - registerUserKeys - Register User Keys Test Case - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - - - deployVirtualMachine - deploy vm1 test case - true - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - displayname - ResourceLimits-VM-1 - - - group - group1 - - - networkids - networkid - - - - - id - vmid1 - - - - - - deployVirtualMachine - deploy vm2 test case - true - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - displayname - ResourceLimits-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - vmid2 - - - - - - - - associateIpAddress - Associate second IP Test Case - true - - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - - ipaddress - ipid - - - - - - - - - - createVolume - Create Fifth Volume Test Case - true - - - - name - FifthVolume - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - diskofferingid - globaldiskofferingid - - - - - - id - volid1 - - - - - - - attachVolume - - - id - volid1 - - - virtualmachineid - vmid1 - - - - - - createVolume - Create Sixth Volume Test Case - true - - - name - SixthVolume - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - diskofferingid - globaldiskofferingid - - - - - id - volid2 - - - - - - - attachVolume - - - id - volid2 - - - virtualmachineid - vmid2 - - - - - - - createSnapshot - Create First Snapshot Test Case - true - - - volumeid - volid1 - - - - - id - snpid - - - - - - createSnapshot - Create Second Snapshot Test Case - true - - - volumeid - volid2 - - - - - id - snpid - - - - - - - registerTemplate - Create First Template Test Case - true - - - name - FirstTemplate - - - displaytext - Firsttemplate - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - format - vhd - - - ostypeid - 41 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - id - templid1 - - - - - - registerTemplate - Create Second Template Test Case - true - - - name - SecondTemplate - - - displaytext - Secondtemplate - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - format - vhd - - - ostypeid - 41 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - id - templid2 - - - - - - - - deployVirtualMachine - Deploy VM3 Failure test case - true - true - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - 2 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - networkids - network - - - - - id - vmid3 - - - - - - - - associateIpAddress - Associate Second IP Failure Test Case - true - true - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - - ipaddress - ipid - - - - - - - - - createVolume - Create Seventh Volume Failure Test Case - true - true - - - name - SeventhVolume - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - diskofferingid - globaldiskofferingid - - - - - - id - volid3 - - - - - - - listVolumes - - - virtualmachineid - vmid1 - - - type - ROOT - - - - - id - rootvolumeid - - - - - - - createSnapshot - Create Third Snapshot Failure Test Case - true - true - - - volumeid - rootvolumeid - - - - - id - snpid - - - - - - - - registerTemplate - Create Third Template Failure Test Case - true - true - - - name - ThirdTemplate - - - displaytext - Thirdtemplate - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - format - vhd - - - ostypeid - 41 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - id - templid3 - - - - - - - deleteAccount - Clean up account test case - - - - id - accountname - - - - - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - email - newadmin@gmail.com - - - firstname - newadmin - - - lastname - newadminlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountname - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - id - userid - - - - - - - - updateResourceLimit - Failure in Setting Up VMs Limit for Admin Account Test Case - true - - - resourcetype - 0 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 0 - - - max - 2 - - - - - - - updateResourceLimit - Failure in Setting Up Public-Ips Limit for Admin Account Test Case - true - - - resourcetype - 1 - - - max - 1 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 1 - - - max - 1 - - - - - - - updateResourceLimit - Failure in Setting Up Volumes Limit for Admin Account Test Case - true - - - resourcetype - 2 - - - max - 6 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 2 - - - max - 6 - - - - - - - updateResourceLimit - Failure in Setting up Snapshots Limit for Admin Account Test Case - true - - - resourcetype - 3 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 3 - - - max - 2 - - - - - - - updateResourceLimit - Failure in Seting Up Templates Limit for Admin Account Test Case - true - - - resourcetype - 4 - - - max - 2 - - - account - accountname - - - domainid - 1 - - - - - resourcetype - 4 - - - max - 2 - - - - - - - - registerUserKeys - Register User Keys Test Case - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - - - deployVirtualMachine - deploy vm1 test case - true - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - displayname - SnapshotTest-Admin-VM-1 - - - group - group1 - - - networkids - networkid - - - - - id - vmid1 - - - - - - deployVirtualMachine - deploy vm2 test case - true - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - displayname - SnapshotTest-Admin-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - vmid2 - - - - - - - - associateIpAddress - Associate second IP Test Case - true - - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - - ipaddress - ipid - - - - - - - createVolume - Create Fifth Volume Test Case - true - - - - name - AdminsFifthVolume - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - diskofferingid - globaldiskofferingid - - - - - - id - volid1 - - - - - - - attachVolume - - - id - volid1 - - - virtualmachineid - vmid1 - - - - - - createVolume - Create Sixth Volume Test Case - true - - - name - AdminsSixthVolume - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - diskofferingid - globaldiskofferingid - - - - - id - volid2 - - - - - - - attachVolume - - - id - volid2 - - - virtualmachineid - vmid2 - - - - - - - createSnapshot - Create First Snapshot Test Case - true - - - volumeid - volid1 - - - - - id - snpid - - - - - - createSnapshot - Create Second Snapshot Test Case - true - - - volumeid - volid2 - - - - - id - snpid - - - - - - - registerTemplate - Create First Template Test Case - true - - - name - AdminsFirstTemplate - - - displaytext - AdminsFirsttemplate - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - format - vhd - - - ostypeid - 41 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - id - templid1 - - - - - - registerTemplate - Create Second Template Test Case - true - - - name - AdminsSecondTemplate - - - displaytext - AdminsSecondtemplate - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - format - - - - ostypeid - 41 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - id - templid2 - - - - - - - - deployVirtualMachine - Deploy VM3 Failure Avoidance test case - true - - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - displayname - SnapshotTest-Admin-VM-3 - - - group - group1 - - - networkids - networkid - - - - - id - vmid3 - - - - - - - - associateIpAddress - Associate Second IP Failure Avoidance Test Case - true - - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - - ipaddress - ipid - - - - - - - - - createVolume - Create Seventh Volume Failure Avoidance Test Case - true - - - - name - AdminsSeventhVolume - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - diskofferingid - globaldiskofferingid - - - - - - id - volid3 - - - - - - - - createSnapshot - Create Third Snapshot Failure Test Case - true - true - - - volumeid - volid1 - - - - - id - snpid - - - - - - - - registerTemplate - Create Third Template Failure Avoidance Test Case - true - - - - name - AdminsThirdTemplate - - - displaytext - AdminsThirdtemplate - - - url - http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 - - - format - vhd - - - ostypeid - 41 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - hypervisor - globalhypervisortype - - - - - - id - templid3 - - - - - - - deleteAccount - Clean up admin account test case - - - - id - accountid - - - - - + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + email + user@gmail.com + + + firstname + user + + + lastname + userlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountname + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + id + userid + + + + + + + + + + + updateResourceLimit + Setup VMs Limit for Account Test Case + + + resourcetype + 0 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 0 + + + max + 2 + + + + + + + updateResourceLimit + Setup Public-Ips Limit for Account Test Case + + + resourcetype + 1 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 1 + + + max + 1 + + + + + + + updateResourceLimit + Setup Volumes Limit for Account Test Case + + + resourcetype + 2 + + + max + 6 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 2 + + + max + 6 + + + + + + + updateResourceLimit + Setup Snapshots Limit for Account Test Case + + + resourcetype + 3 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 3 + + + max + 2 + + + + + + + updateResourceLimit + Setup Templates Limit for Account Test Case + + + resourcetype + 4 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 4 + + + max + 2 + + + + + + + + registerUserKeys + Register User Keys Test Case + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + + + deployVirtualMachine + deploy vm1 test case + true + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + displayname + ResourceLimits-VM-1 + + + group + group1 + + + networkids + networkid + + + + + id + vmid1 + + + + + + deployVirtualMachine + deploy vm2 test case + true + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + displayname + ResourceLimits-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + vmid2 + + + + + + + + associateIpAddress + Associate second IP Test Case + true + + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + + ipaddress + ipid + + + + + + + + + + createVolume + Create Fifth Volume Test Case + true + + + + name + FifthVolume + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + diskofferingid + globaldiskofferingid + + + + + + id + volid1 + + + + + + + attachVolume + + + id + volid1 + + + virtualmachineid + vmid1 + + + + + + createVolume + Create Sixth Volume Test Case + true + + + name + SixthVolume + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + diskofferingid + globaldiskofferingid + + + + + id + volid2 + + + + + + + attachVolume + + + id + volid2 + + + virtualmachineid + vmid2 + + + + + + + createSnapshot + Create First Snapshot Test Case + true + + + volumeid + volid1 + + + + + id + snpid + + + + + + createSnapshot + Create Second Snapshot Test Case + true + + + volumeid + volid2 + + + + + id + snpid + + + + + + + registerTemplate + Create First Template Test Case + true + + + name + FirstTemplate + + + displaytext + Firsttemplate + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + format + vhd + + + ostypeid + 41 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + id + templid1 + + + + + + registerTemplate + Create Second Template Test Case + true + + + name + SecondTemplate + + + displaytext + Secondtemplate + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + format + vhd + + + ostypeid + 41 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + id + templid2 + + + + + + + + deployVirtualMachine + Deploy VM3 Failure test case + true + true + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + 2 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + networkids + network + + + + + id + vmid3 + + + + + + + + associateIpAddress + Associate Second IP Failure Test Case + true + true + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + + ipaddress + ipid + + + + + + + + + createVolume + Create Seventh Volume Failure Test Case + true + true + + + name + SeventhVolume + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + diskofferingid + globaldiskofferingid + + + + + + id + volid3 + + + + + + + listVolumes + + + virtualmachineid + vmid1 + + + type + ROOT + + + + + id + rootvolumeid + + + + + + + createSnapshot + Create Third Snapshot Failure Test Case + true + true + + + volumeid + rootvolumeid + + + + + id + snpid + + + + + + + + registerTemplate + Create Third Template Failure Test Case + true + true + + + name + ThirdTemplate + + + displaytext + Thirdtemplate + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + format + vhd + + + ostypeid + 41 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + id + templid3 + + + + + + + deleteAccount + Clean up account test case + + + + id + accountname + + + + + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + email + newadmin@gmail.com + + + firstname + newadmin + + + lastname + newadminlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountname + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + id + userid + + + + + + + + updateResourceLimit + Failure in Setting Up VMs Limit for Admin Account Test Case + true + + + resourcetype + 0 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 0 + + + max + 2 + + + + + + + updateResourceLimit + Failure in Setting Up Public-Ips Limit for Admin Account Test Case + true + + + resourcetype + 1 + + + max + 1 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 1 + + + max + 1 + + + + + + + updateResourceLimit + Failure in Setting Up Volumes Limit for Admin Account Test Case + true + + + resourcetype + 2 + + + max + 6 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 2 + + + max + 6 + + + + + + + updateResourceLimit + Failure in Setting up Snapshots Limit for Admin Account Test Case + true + + + resourcetype + 3 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 3 + + + max + 2 + + + + + + + updateResourceLimit + Failure in Seting Up Templates Limit for Admin Account Test Case + true + + + resourcetype + 4 + + + max + 2 + + + account + accountname + + + domainid + 1 + + + + + resourcetype + 4 + + + max + 2 + + + + + + + + registerUserKeys + Register User Keys Test Case + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + + + deployVirtualMachine + deploy vm1 test case + true + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + displayname + SnapshotTest-Admin-VM-1 + + + group + group1 + + + networkids + networkid + + + + + id + vmid1 + + + + + + deployVirtualMachine + deploy vm2 test case + true + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + displayname + SnapshotTest-Admin-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + vmid2 + + + + + + + + associateIpAddress + Associate second IP Test Case + true + + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + + ipaddress + ipid + + + + + + + createVolume + Create Fifth Volume Test Case + true + + + + name + AdminsFifthVolume + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + diskofferingid + globaldiskofferingid + + + + + + id + volid1 + + + + + + + attachVolume + + + id + volid1 + + + virtualmachineid + vmid1 + + + + + + createVolume + Create Sixth Volume Test Case + true + + + name + AdminsSixthVolume + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + diskofferingid + globaldiskofferingid + + + + + id + volid2 + + + + + + + attachVolume + + + id + volid2 + + + virtualmachineid + vmid2 + + + + + + + createSnapshot + Create First Snapshot Test Case + true + + + volumeid + volid1 + + + + + id + snpid + + + + + + createSnapshot + Create Second Snapshot Test Case + true + + + volumeid + volid2 + + + + + id + snpid + + + + + + + registerTemplate + Create First Template Test Case + true + + + name + AdminsFirstTemplate + + + displaytext + AdminsFirsttemplate + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + format + vhd + + + ostypeid + 41 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + id + templid1 + + + + + + registerTemplate + Create Second Template Test Case + true + + + name + AdminsSecondTemplate + + + displaytext + AdminsSecondtemplate + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + format + + + + ostypeid + 41 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + id + templid2 + + + + + + + + deployVirtualMachine + Deploy VM3 Failure Avoidance test case + true + + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + displayname + SnapshotTest-Admin-VM-3 + + + group + group1 + + + networkids + networkid + + + + + id + vmid3 + + + + + + + + associateIpAddress + Associate Second IP Failure Avoidance Test Case + true + + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + + ipaddress + ipid + + + + + + + + + createVolume + Create Seventh Volume Failure Avoidance Test Case + true + + + + name + AdminsSeventhVolume + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + diskofferingid + globaldiskofferingid + + + + + + id + volid3 + + + + + + + + createSnapshot + Create Third Snapshot Failure Test Case + true + true + + + volumeid + volid1 + + + + + id + snpid + + + + + + + + registerTemplate + Create Third Template Failure Avoidance Test Case + true + + + + name + AdminsThirdTemplate + + + displaytext + AdminsThirdtemplate + + + url + http://192.168.10.231/templates/routing/latest/systemvm.vhd.bz2 + + + format + vhd + + + ostypeid + 41 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + hypervisor + globalhypervisortype + + + + + + id + templid3 + + + + + + + deleteAccount + Clean up admin account test case + + + + id + accountid + + + + + diff --git a/test/metadata/func/roughflatstress.xml b/test/metadata/func/roughflatstress.xml index 291d434348ac..791379217283 100644 --- a/test/metadata/func/roughflatstress.xml +++ b/test/metadata/func/roughflatstress.xml @@ -1,691 +1,691 @@ - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - email - firstuser@gmail.com - - - firstname - firstuser - - - lastname - firstuserlastname - - - username - getusernameparam1 - - - password - password - - - - - id - getaccountid - - - state - enabled - - - - - - listUsers - [List the User of the account] - - - account - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - username - getusernameparam1 - - - - - id - getuserid1 - - - state - enabled - - - - - - registerUserKeys - Registering the First User - - - id - getuserid1 - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createSecurityGroup - true - Creating First Security group for the Account - - - name - getsecuritygroupname1 - - - description - - - - - id - getsecuritygroupid1 - - - - - - authorizeSecurityGroupIngress - true - Authorizing First network group ingress rule for the First Security Group - - - securitygroupid - getsecuritygroupid1 - - - cidrlist - globalcidrlist - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - - - - listSecurityGroups - true - List the Security group and get the ingress rule ID - - - securitygroupid - getsecuritygroupid1 - - - - - ruleid - getingressruleid1 - - - - - - deployVirtualMachine - true - Deploying VM for the First Account - - - zoneId - globalzoneid - - - hypervisor - globalhypervisortype - - - templateId - globaltemplateid - - - serviceOfferingId - globalserviceofferingid - - - securitygroupids - getsecuritygroupid1 - - - - - id - getvmid1 - - - ipaddress - getvmip1 - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getvmip1 - - - p - password - - - - - - sleep.sh - - Sleep for 1 min - - - s - 60 - - - - - - revokeSecurityGroupIngress - true - Revoking network group igress - - - id - getingressruleid1 - - - - - - - - - createSecurityGroup - true - Creating a Second Security group for the Account - - - name - getsecuritygroupname2 - - - description - - - - - id - getsecuritygroupid2 - - - - - - authorizeSecurityGroupIngress - true - Authorizing network group ingress for the second Security group - - - securitygroupid - getsecuritygroupid2 - - - usersecuritygrouplist[0].account - accountname - - - usersecuritygrouplist[0].group - getsecuritygroupname1 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - - - - - authorizeSecurityGroupIngress - true - Authorizing one more network group ingress rule for the Second Security Group - - - securitygroupid - getsecuritygroupid2 - - - cidrlist - globalcidrlist - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - - - - listSecurityGroups - true - List the Security group and get the first ingress rule ID - - - securitygroupid - getsecuritygroupid2 - - - - - ruleid - getingressruleid2 - - - - - - deployVirtualMachine - true - Deploying Second VM for the First Account - - - zoneId - globalzoneid - - - hypervisor - globalhypervisortype - - - templateId - globaltemplateid - - - serviceOfferingId - flatnetworkserviceofferingid - - - securitygroupids - getsecuritygroupid2 - - - - - id - getvmid2 - - - ipaddress - getvmip2 - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getvmip2 - - - p - password - - - - - - sleep.sh - - Sleep for 1 min - - - s - 60 - - - - - - revokeSecurityGroupIngress - true - Revoking Second Network group's First ingress rule - - - id - getingressruleid2 - - - - - - listSecurityGroups - true - List the Security group and get the second ingress rule ID - - - securitygroupid - getsecuritygroupid2 - - - - - ruleid - getingressruleid2 - - - - - - revokeSecurityGroupIngress - true - Revoking Second network group's second ingress rule - - - id - getingressruleid2 - - - - - - - destroyVirtualMachine - true - Destroying virtual machine - - - id - getvmid1 - - - - - - sleep.sh - - Sleep for 1 min - - - s - 60 - - - - - - destroyVirtualMachine - true - Destroying virtual machine - - - id - getvmid2 - - - - - - sleep.sh - - Sleep for 1 min - - - s - 60 - - - - - - - - deleteUser - Deleting the user - - - id - getuserid1 - - - - - - deleteAccount - Delete Account - - - id - getaccountid - - - - - + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + email + firstuser@gmail.com + + + firstname + firstuser + + + lastname + firstuserlastname + + + username + getusernameparam1 + + + password + password + + + + + id + getaccountid + + + state + enabled + + + + + + listUsers + [List the User of the account] + + + account + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + username + getusernameparam1 + + + + + id + getuserid1 + + + state + enabled + + + + + + registerUserKeys + Registering the First User + + + id + getuserid1 + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createSecurityGroup + true + Creating First Security group for the Account + + + name + getsecuritygroupname1 + + + description + + + + + id + getsecuritygroupid1 + + + + + + authorizeSecurityGroupIngress + true + Authorizing First network group ingress rule for the First Security Group + + + securitygroupid + getsecuritygroupid1 + + + cidrlist + globalcidrlist + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + + + + listSecurityGroups + true + List the Security group and get the ingress rule ID + + + securitygroupid + getsecuritygroupid1 + + + + + ruleid + getingressruleid1 + + + + + + deployVirtualMachine + true + Deploying VM for the First Account + + + zoneId + globalzoneid + + + hypervisor + globalhypervisortype + + + templateId + globaltemplateid + + + serviceOfferingId + globalserviceofferingid + + + securitygroupids + getsecuritygroupid1 + + + + + id + getvmid1 + + + ipaddress + getvmip1 + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getvmip1 + + + p + password + + + + + + sleep.sh + + Sleep for 1 min + + + s + 60 + + + + + + revokeSecurityGroupIngress + true + Revoking network group igress + + + id + getingressruleid1 + + + + + + + + + createSecurityGroup + true + Creating a Second Security group for the Account + + + name + getsecuritygroupname2 + + + description + + + + + id + getsecuritygroupid2 + + + + + + authorizeSecurityGroupIngress + true + Authorizing network group ingress for the second Security group + + + securitygroupid + getsecuritygroupid2 + + + usersecuritygrouplist[0].account + accountname + + + usersecuritygrouplist[0].group + getsecuritygroupname1 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + + + + + authorizeSecurityGroupIngress + true + Authorizing one more network group ingress rule for the Second Security Group + + + securitygroupid + getsecuritygroupid2 + + + cidrlist + globalcidrlist + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + + + + listSecurityGroups + true + List the Security group and get the first ingress rule ID + + + securitygroupid + getsecuritygroupid2 + + + + + ruleid + getingressruleid2 + + + + + + deployVirtualMachine + true + Deploying Second VM for the First Account + + + zoneId + globalzoneid + + + hypervisor + globalhypervisortype + + + templateId + globaltemplateid + + + serviceOfferingId + flatnetworkserviceofferingid + + + securitygroupids + getsecuritygroupid2 + + + + + id + getvmid2 + + + ipaddress + getvmip2 + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getvmip2 + + + p + password + + + + + + sleep.sh + + Sleep for 1 min + + + s + 60 + + + + + + revokeSecurityGroupIngress + true + Revoking Second Network group's First ingress rule + + + id + getingressruleid2 + + + + + + listSecurityGroups + true + List the Security group and get the second ingress rule ID + + + securitygroupid + getsecuritygroupid2 + + + + + ruleid + getingressruleid2 + + + + + + revokeSecurityGroupIngress + true + Revoking Second network group's second ingress rule + + + id + getingressruleid2 + + + + + + + destroyVirtualMachine + true + Destroying virtual machine + + + id + getvmid1 + + + + + + sleep.sh + + Sleep for 1 min + + + s + 60 + + + + + + destroyVirtualMachine + true + Destroying virtual machine + + + id + getvmid2 + + + + + + sleep.sh + + Sleep for 1 min + + + s + 60 + + + + + + + + deleteUser + Deleting the user + + + id + getuserid1 + + + + + + deleteAccount + Delete Account + + + id + getaccountid + + + + + diff --git a/test/metadata/func/roughregression.xml b/test/metadata/func/roughregression.xml index 64c120a8bd4d..8a37dad35549 100644 --- a/test/metadata/func/roughregression.xml +++ b/test/metadata/func/roughregression.xml @@ -1,6012 +1,6012 @@ - - - - listDomains - [List Domains] - - - id - globaldomainid - - - - - name - getdomainname1 - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - email - firstuser@gmail.com - - - firstname - firstuser - - - lastname - firstuserlastname - - - username - getusernameparam1 - - - password - password - - - - - id - getaccountid - - - state - enabled - - - - - - registerIso - [] registerIsotestcase - - - name - CentOS-5-5-ISO - - - displayText - CentOS-5-5-ISO - - - url - http://nfs1.lab.vmops.com/isos_64bit/CentOS-5.5-x86_64-bin-DVDs/CentOS-5.5-x86_64-bin-DVD-1of2.iso - - - zoneId - -1 - - - isPublic - true - - - bootable - true - - - osTypeId - 112 - - - isfeatured - true - - - account - accountname - - - domainid - globaldomainid - - - - - id - getisoid1 - - - - - - listIsos - [] listIsosTestCase - - - isofilter - self - - - id - getisoid1 - - - zoneid - 1 - - - - - id - getisoid1 - - - name - CentOS-5-5-ISO - - - displayText - CentOS-5-5-ISO - - - zoneId - 1 - - - isPublic - true - - - bootable - true - - - osTypeId - 112 - - - isfeatured - true - - - account - accountname - - - domainid - globaldomainid - - - domain - getdomainname1 - - - - - - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - globaldomainid - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - getnetworkid1 - - - - - - - - listAccounts - 950 [List the Account of the User created in (3)] - - - name - accountname - - - - - id - getaccountid - - - name - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - id - getuserid1 - - - username - getusernameparam1 - - - - - - - createUser - 3 [Create User Command] - - - account - accountname - - - accounttype - globalaccttype - - - username - getusernameparam2 - - - password - password - - - firstname - seconduser - - - lastname - seconduserlastname - - - email - seconduser@gmail.com - - - domainid - globaldomainid - - - - - id - getuserid2 - - - - - - - - updateUser - 6 [Update the User created in (3)] - - - id - getuserid1 - - - username - getusernameparam3 - - - password - password - - - firstname - updatedfirstuser - - - lastname - updatedfirstuserlastname - - - email - updatedfirstuser@gmail.com - - - timezone - PST - - - - - id - getuserid1 - - - username - getusernameparam3 - - - - - - - listUsers - 5 [List the Updated User updated in (6)] - - - id - getuserid1 - - - - - id - getuserid1 - - - username - getusernameparam3 - - - account - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - timezone - PST - - - - - - - disableUser - 428 [Disable the User created in (3)] - - - id - getuserid1 - - - - - id - getuserid1 - - - username - getusernameparam3 - - - firstname - updatedfirstuser - - - lastname - updatedfirstuserlastname - - - email - updatedfirstuser@gmail.com - - - state - disabled - - - account - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - timezone - PST - - - - - - - enableUser - 9 [Enable the User created in (3)] - - - id - getuserid1 - - - - - id - getuserid1 - - - username - getusernameparam3 - - - firstname - updatednimbus - - - lastname - updatednimbuslastname - - - email - updatednimbus@gmail.com - - - created - createtimestamp - - - state - enabled - - - account - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - timezone - PST - - - - - - - disableAccount - [Disable the Account] - - - account - accountname - - - domainid - globaldomainid - - - lock - true - - - - - id - getaccountid - - - name - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - locked - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - globaldomainid - - - - - id - getaccountid - - - name - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - disableAccount - [Lock the Account] - - - account - accountname - - - domainid - globaldomainid - - - lock - false - - - - - id - getaccountid - - - name - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - getdomainname1 - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - disabled - - - - - - - enableAccount - [Enable the Locked Account] - - - account - accountname - - - domainid - globaldomainid - - - - - id - getaccountid - - - name - accountname - - - accounttype - globalaccttype - - - domainid - globaldomainid - - - domain - ROOT - - - receivedbytes - 0 - - - sentbytes - 0 - - - vmlimit - Unlimited - - - vmtotal - 0 - - - vmavailable - Unlimited - - - ipavailable - Unlimited - - - iptotal - 0 - - - iplimit - Unlimited - - - volumelimit - Unlimited - - - volumetotal - 0 - - - volumeavailable - Unlimited - - - snapshotlimit - Unlimited - - - snapshottotal - 0 - - - snapshotavailable - Unlimited - - - templatelimit - Unlimited - - - templatetotal - 0 - - - templateavailable - Unlimited - - - vmstopped - 0 - - - vmrunning - 0 - - - state - enabled - - - - - - - - createDomain - [Create Domain] - - - name - getdomainname2 - - - parentdomainid - globaldomainid - - - - - id - getdomainid1 - - - - - - - listDomains - 11 [List Domains] - - - name - getdomainname2 - - - - - - id - getdomainid1 - - - name - getdomainname2 - - - level - getcurrentlevel1 - - - parentdomainid - globaldomainid - - - parentdomainname - getdomainname1 - - - haschild - false - - - - - - - updateDomain - 952 [Update the Domain created in (10)] - - - id - getdomainid1 - - - name - getdomainname2 - - - - - id - getdomainid1 - - - name - getdomainname2 - - - level - getcurrentlevel1 - - - parentdomainid - globaldomainid - - - parentdomainname - getdomainname1 - - - haschild - false - - - - - - - - listDomainChildren - - [List One of the Children of the Parent Domain] - - - id - globaldomainid - - - - - domain - 0 - - id - getdomainid1 - - - name - getdomainname2 - - - level - getcurrentlevel1 - - - parentdomainid - globaldomainid - - - parentdomainname - getdomainname1 - - - haschild - false - - - - - - - deleteDomain - 654 [Delete Domain created in (10)] - - - id - getdomainid1 - - - cleanup - true - - - - - - success - true - - - - - - - listTemplates - 27 [List Templates - List System Template] - - - templatefilter - self - - - id - 1 - - - - - template - 0 - - id - debian50templateid - - - name - debian50templatename - - - displaytext - debian50templatedisplaytext - - - ispublic - debian50templateispublic - - - created - debian50templatecreated - - - isready - true - - - passwordenabled - debian50templatepasswordenabled - - - format - VHD - - - isfeatured - false - - - crossZones - true - - - ostypeid - debian50templateostypeid - - - ostypename - debian50templateostypename - - - size - debian50templatesize - - - account - system - - - zoneid - globalzoneid - - - zonename - globalzonename - - - status - Download Complete - - - templatetype - SYSTEM - - - hypervisor - XenServer - - - domain - ROOT - - - domainid - 1 - - - - - - - - - listServiceOfferings - 28 [List the Small Instance Service Offering] - - - id - globalserviceofferingid - - - - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - listServiceOfferings - 876 [Verify the First Service Offering] - - - name - smallofferingname - - - - serviceoffering - 0 - - id - smallofferingid - - - name - smallofferingname - - - displaytext - smallofferingdisplaytext - - - cpunumber - smallofferingcpunumber - - - cpuspeed - smallofferingcpuspeed - - - memory - smallofferingmemory - - - storagetype - smallofferingstoragetype - - - offerha - false - - - - - - - - listZones - 39 [List the Global Zone with id-2] - - zone - 0 - - id - globalzoneid - - - name - globalzonename - - - dns1 - dns1 - - - dns2 - dns2 - - - internaldns1 - internaldns1 - - - vlan - - - guestcidraddress - - - networktype - Advanced - - - - - - - - - - createServiceOffering - [1] createServiceOffering-TestCase - - - issystem - false - - - name - LittleInstance - - - displaytext - LittleInstance - - - storagetype - shared - - - cpuNumber - 1 - - - cpuSpeed - 100 - - - memory - 128 - - - offerha - false - - - limitcpuuse - false - - - - - id - servofferid - - - - - - - -createDiskOffering - [1] createDiskOffering-TestCase - - - isMirrored - false - - - name - Little - - - displaytext - LittleDisk1GB - - - customized - false - - - disksize - 1 - - - - - id - diskofferid - - - - - - - - deployVirtualMachine - [2] deployVirtualMachine-TestCase - - - zoneId - globalzoneid - - - hypervisor - globalhypervisortype - - - templateId - globaltemplateid - - - serviceOfferingId - servofferid - - - networkIds - getnetworkid1 - - - diskOfferingId - diskofferid - - - displayname - FirstVM - - - group - FirstVM - - - account - accountname - - - domainid - globaldomainid - - - - - id - getvmid1 - - - - - - updateVirtualMachine - [3] updateVirtualMachine-TestCase - - - id - getvmid1 - - - displayName - First-VM - - - group - First-VM - - - haenable - false - - - ostypeid - 12 - - - - - - - - associateIpAddress - [4] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - enableStaticNat - [5] enableStaticNat-TestCase - - - ipaddressid - getpublicipid1 - - - virtualmachineid - getvmid1 - - - - - - createIpForwardingRule - [6] createIpForwardingRule-TestCase - - - ipaddressid - getpublicipid1 - - - startPort - 1 - - - endPort - 100 - - - protocol - TCP - - - - - id - getipfwdruleid1 - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getipaddress1 - - - p - password - - - - - - deleteIpForwardingRule - [7] deleteIpForwardingRule-TestCase - - - id - getipfwdruleid1 - - - - - - disableStaticNat - [8] disableStaticNat-TestCase - - - ipaddressid - getpublicipid1 - - - - - - disassociateIpAddress - [9] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [10] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - createPortForwardingRule - [11] createPortForwardingRule-TestCase - - - ipaddressid - getpublicipid1 - - - cidrlist - 0.0.0.0/0 - - - publicport - 22 - - - publicendport - 30 - - - privateport - 22 - - - privateendport - 30 - - - protocol - TCP - - - virtualmachineid - getvmid1 - - - - - id - getportfwdruleid1 - - - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getipaddress1 - - - p - password - - - - - - deletePortForwardingRule - [12] deletePortForwardingRule-TestCase - - - id - getportfwdruleid1 - - - - - - disassociateIpAddress - [13] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [14] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - createLoadBalancerRule - [15] createLoadBalancerRule-TestCase - - - publicipid - getpublicipid1 - - - name - LBRule-1 - - - publicport - 80 - - - privateport - 80 - - - algorithm - roundrobin - - - - - id - getldblcrule1 - - - - - - assignToLoadBalancerRule - [16] assignToLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - getvmid1 - - - - - - - removeFromLoadBalancerRule - [18] removeFromLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - getvmid1 - - - - - - updateLoadBalancerRule - [19] updateLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - name - LBRuleUpdated-1 - - - algorithm - roundrobin - - - - - - disassociateIpAddress - [20] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [14] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - - - - createLoadBalancerRule - [21] createLoadBalancerRule-TestCase - - - publicipid - getpublicipid1 - - - name - LBRule-2 - - - publicport - 80 - - - privateport - 80 - - - algorithm - roundrobin - - - - - id - getldblcrule1 - - - - - - assignToLoadBalancerRule - [22] assignToLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - getvmid1 - - - - - - - - deleteLoadBalancerRule - [24] deleteLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - - - - disassociateIpAddress - [25] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - - listVolumes - [26] listROOTVolumes-TestCase - - - virtualmachineid - getvmid1 - - - type - ROOT - - - - - id - getrootvolumeid1 - - - - - - createSnapshot - [26] createSnapshotofROOTVolume-TestCase - - - volumeid - getrootvolumeid1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getsnapshotid1 - - - - - - listSnapshots - [27] listROOTVolumeSnapshot-TestCase - - - volumeid - getrootvolumeid1 - - - - - - listVolumes - [26] listDATADISKVolumes-TestCase - - - virtualmachineid - getvmid1 - - - type - DATADISK - - - - - id - getdatavolumeid1 - - - - - - createSnapshot - [28] createSnapshotofDATADISKVolume-TestCase - - - volumeid - getdatavolumeid1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getsnapshotid2 - - - - - - listSnapshots - [29] listDATADISKVolumeSnapshot-TestCase - - - volumeid - getdatavolumeid1 - - - - - - createTemplate - [30] createTemplateFromSnapshotTakenofROOTVolume-TestCase - - - snapshotid - getsnapshotid1 - - - name - TemplateFromSnapshot-1 - - - displaytext - TemplateFromSnapshot-1 - - - ostypeid - 12 - - - isPublic - true - - - passwordEnabled - false - - - isfeatured - true - - - - - id - gettemplateid1 - - - - - - createTemplate - [31] createTemplateFromSnapshotTakenofDATADISKVolume-TestCase - - - snapshotid - getsnapshotid2 - - - name - TemplateFromSnapshot-2 - - - displaytext - TemplateFromSnapshot-2 - - - ostypeid - 12 - - - isPublic - true - - - passwordEnabled - false - - - isfeatured - true - - - - - id - gettemplateid2 - - - - - - createVolume - [32] createVolumeFromSnapshotTakenofROOTVolume-TestCase - - - snapshotid - getsnapshotid1 - - - name - VlmfromSnapshot-1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getdatavolumeid2 - - - - - - createVolume - [33] createVolumeFromSnapshotTakenofDATADISKVolume-TestCase - - - snapshotid - getsnapshotid2 - - - name - VlmfromSnapshot-2 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getdatavolumeid3 - - - - - - - attachVolume - [34] attachVolume1-TestCase - - - id - getdatavolumeid2 - - - virtualMachineId - getvmid1 - - - - - - attachVolume - [35] attachVolume2-TestCase - - - id - getdatavolumeid3 - - - virtualMachineId - getvmid1 - - - - - - - - detachVolume - [36] detachVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - detachVolume - [37] detachVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - attachVolume - [34] attachVolume1-TestCase - - - id - getdatavolumeid2 - - - virtualMachineId - getvmid1 - - - - - - attachVolume - [35] attachVolume2-TestCase - - - id - getdatavolumeid3 - - - virtualMachineId - getvmid1 - - - - - - stopVirtualMachine - [40] stopVirtualMachine-TestCase - - - id - getvmid1 - - - - - - updateVirtualMachine - [41] updateVirtualMachine-TestCase - - - id - getvmid1 - - - displayName - First-VM-1-1 - - - group - First-VM-1-1 - - - haenable - false - - - ostypeid - 12 - - - - - - detachVolume - [36] detachVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - detachVolume - [37] detachVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - createTemplate - [44] createTemplateFromROOTVolume-TestCase - - - volumeId - getrootvolumeid1 - - - name - TemplateFromRootVolume-3 - - - displayText - TemplateFromRootVolume-3 - - - osTypeId - 12 - - - isPublic - true - - - passwordEnabled - false - - - - - id - gettemplateid3 - - - - - - startVirtualMachine - [45] startVirtualMachine-TestCase - - - id - getvmid1 - - - - - - rebootVirtualMachine - [46] rebootVirtualMachine-TestCase - - - id - getvmid1 - - - - - - createSnapshotPolicy - [47] createHOURLYSnapshotPolicyForRootVolume-TestCase - - - intervaltype - hourly - - - schedule - 11 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [48] createDAILYSnapshotPolicy-TestCase - - - intervaltype - daily - - - schedule - 13:07 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [49] createWEEKLYSnapshotPolicy-TestCase - - - intervaltype - weekly - - - schedule - 16:07:4 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [50] createMONTHLYSnapshotPolicy-TestCase - - - intervaltype - monthly - - - schedule - 18:19:1 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - id - getmonthlysnapshotpolicyid1 - - - - - - deleteSnapshotPolicies - [51] deleteMONTHLYSnapshotPolicies-TestCase - - - id - getmonthlysnapshotpolicyid1 - - - - - - deleteVolume - [52] deleteVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - deleteVolume - [53] deleteVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - - - destroyVirtualMachine - [54] destroyVirtualMachine-TestCase - - - id - getvmid1 - - - - - - recoverVirtualMachine - [55] recoverVirtualMachine-TestCase - - - id - getvmid1 - - - - - - startVirtualMachine - [56] startVirtualMachine-TestCase - - - id - getvmid1 - - - - - - rebootVirtualMachine - [57] rebootVirtualMachine-TestCase - - - id - getvmid1 - - - - - - stopVirtualMachine - [58] stopVirtualMachine-TestCase - - - id - getvmid1 - - - - - - destroyVirtualMachine - [59] destroyVirtualMachine-TestCase - - - id - getvmid1 - - - - - - recoverVirtualMachine - [60] recoverVirtualMachine-TestCase - - - id - getvmid1 - - - - - - changeServiceForVirtualMachine - [61] changeServiceForVirtualMachine-TestCase - - - id - getvmid1 - - - serviceOfferingId - globalchangeservoff - - - - - - startVirtualMachine - [62] startVirtualMachine-TestCase - - - id - getvmid1 - - - - - - rebootVirtualMachine - [63] rebootVirtualMachine-TestCase - - - id - getvmid1 - - - - - - stopVirtualMachine - [64] stopVirtualMachine-TestCase - - - id - getvmid1 - - - - - - destroyVirtualMachine - [65] destroyVirtualMachine-TestCase - - - id - getvmid1 - - - - - - deployVirtualMachine - [66] deployVirtualMachineFromTemplateCreatedFromSnapshotofROOTVolume-TestCase - - - zoneId - globalzoneid - - - hypervisor - globalhypervisortype - - - templateId - gettemplateid1 - - - serviceOfferingId - servofferid - - - networkIds - getnetworkid1 - - - diskOfferingId - diskofferid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getvmid1 - - - - - - deleteTemplate - [67] deleteTemplateofSnapshotTakenofROOTVolume-TestCase - - - id - gettemplateid1 - - - zoneid - globalzoneid - - - - - - deleteTemplate - [68] deleteTemplateofSnapshotTakenofDATAVolume-TestCase - - - id - gettemplateid2 - - - zoneid - globalzoneid - - - - - - deleteTemplate - [69] deleteTemplateCreatedFromROOTVolume-TestCase - - - id - gettemplateid3 - - - zoneid - globalzoneid - - - - - - deleteSnapshot - [70] deleteSnapshot-TestCase - - - id - getsnapshotid1 - - - - - - deleteSnapshot - [71] deleteSnapshot-TestCase - - - id - getsnapshotid2 - - - - - - - - - updateVirtualMachine - [3] updateVirtualMachine-TestCase - - - id - getvmid1 - - - displayName - First-VM - - - group - First-VM - - - haenable - false - - - ostypeid - 12 - - - - - - - - associateIpAddress - [4] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - enableStaticNat - [5] enableStaticNat-TestCase - - - ipaddressid - getpublicipid1 - - - virtualmachineid - getvmid1 - - - - - - createIpForwardingRule - [6] createIpForwardingRule-TestCase - - - ipaddressid - getpublicipid1 - - - startPort - 1 - - - endPort - 100 - - - protocol - TCP - - - - - id - getipfwdruleid1 - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getipaddress1 - - - p - password - - - - - - deleteIpForwardingRule - [7] deleteIpForwardingRule-TestCase - - - id - getipfwdruleid1 - - - - - - disableStaticNat - [8] disableStaticNat-TestCase - - - ipaddressid - getpublicipid1 - - - - - - disassociateIpAddress - [9] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [10] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - createPortForwardingRule - [11] createPortForwardingRule-TestCase - - - ipaddressid - getpublicipid1 - - - cidrlist - 0.0.0.0/0 - - - publicport - 22 - - - publicendport - 30 - - - privateport - 22 - - - privateendport - 30 - - - protocol - TCP - - - virtualmachineid - getvmid1 - - - - - id - getportfwdruleid1 - - - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getipaddress1 - - - p - password - - - - - - deletePortForwardingRule - [12] deletePortForwardingRule-TestCase - - - id - getportfwdruleid1 - - - - - - disassociateIpAddress - [13] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [14] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - createLoadBalancerRule - [15] createLoadBalancerRule-TestCase - - - publicipid - getpublicipid1 - - - name - LBRule-1 - - - publicport - 80 - - - privateport - 80 - - - algorithm - roundrobin - - - - - id - getldblcrule1 - - - - - - assignToLoadBalancerRule - [16] assignToLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - getvmid1 - - - - - - - removeFromLoadBalancerRule - [18] removeFromLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - getvmid1 - - - - - - updateLoadBalancerRule - [19] updateLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - name - LBRuleUpdated-1 - - - algorithm - roundrobin - - - - - - disassociateIpAddress - [20] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [14] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - - - - createLoadBalancerRule - [21] createLoadBalancerRule-TestCase - - - publicipid - getpublicipid1 - - - name - LBRule-2 - - - publicport - 80 - - - privateport - 80 - - - algorithm - roundrobin - - - - - id - getldblcrule1 - - - - - - assignToLoadBalancerRule - [22] assignToLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - getvmid1 - - - - - - - deleteLoadBalancerRule - [24] deleteLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - - - - disassociateIpAddress - [25] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - - listVolumes - [26] listROOTVolumes-TestCase - - - virtualmachineid - getvmid1 - - - type - ROOT - - - - - id - getrootvolumeid1 - - - - - - createSnapshot - [26] createSnapshotofROOTVolume-TestCase - - - volumeid - getrootvolumeid1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getsnapshotid1 - - - - - - listSnapshots - [27] listROOTVolumeSnapshot-TestCase - - - volumeid - getrootvolumeid1 - - - - - - listVolumes - [26] listDATADISKVolumes-TestCase - - - virtualmachineid - getvmid1 - - - type - DATADISK - - - - - id - getdatavolumeid1 - - - - - - createSnapshot - [28] createSnapshotofDATADISKVolume-TestCase - - - volumeid - getdatavolumeid1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getsnapshotid2 - - - - - - listSnapshots - [29] listDATADISKVolumeSnapshot-TestCase - - - volumeid - getdatavolumeid1 - - - - - - createTemplate - [30] createTemplateFromSnapshotTakenofROOTVolume-TestCase - - - snapshotid - getsnapshotid1 - - - name - TemplateFromSnapshot-4 - - - displaytext - TemplateFromSnapshot-4 - - - ostypeid - 12 - - - isPublic - true - - - passwordEnabled - false - - - isfeatured - true - - - - - id - gettemplateid1 - - - - - - createTemplate - [31] createTemplateFromSnapshotTakenofDATADISKVolume-TestCase - - - snapshotid - getsnapshotid2 - - - name - TemplateFromSnapshot-5 - - - displaytext - TemplateFromSnapshot-5 - - - ostypeid - 12 - - - isPublic - true - - - passwordEnabled - false - - - isfeatured - true - - - - - id - gettemplateid2 - - - - - - createVolume - [32] createVolumeFromSnapshotTakenofROOTVolume-TestCase - - - snapshotid - getsnapshotid1 - - - name - VlmfromSnapshot-1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getdatavolumeid2 - - - - - - createVolume - [33] createVolumeFromSnapshotTakenofDATADISKVolume-TestCase - - - snapshotid - getsnapshotid2 - - - name - VlmfromSnapshot-2 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getdatavolumeid3 - - - - - - - attachVolume - [34] attachVolume1-TestCase - - - id - getdatavolumeid2 - - - virtualMachineId - getvmid1 - - - - - - attachVolume - [35] attachVolume2-TestCase - - - id - getdatavolumeid3 - - - virtualMachineId - getvmid1 - - - - - - - - detachVolume - [36] detachVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - detachVolume - [37] detachVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - attachVolume - [34] attachVolume1-TestCase - - - id - getdatavolumeid2 - - - virtualMachineId - getvmid1 - - - - - - attachVolume - [35] attachVolume2-TestCase - - - id - getdatavolumeid3 - - - virtualMachineId - getvmid1 - - - - - - stopVirtualMachine - [40] stopVirtualMachine-TestCase - - - id - getvmid1 - - - - - - updateVirtualMachine - [41] updateVirtualMachine-TestCase - - - id - getvmid1 - - - displayName - First-VM-1-1 - - - group - First-VM-1-1 - - - haenable - false - - - ostypeid - 12 - - - - - - detachVolume - [36] detachVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - detachVolume - [37] detachVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - createTemplate - [44] createTemplateFromROOTVolume-TestCase - - - volumeId - getrootvolumeid1 - - - name - TemplateFromRootVolume-6 - - - displayText - TemplateFromRootVolume-6 - - - osTypeId - 12 - - - isPublic - true - - - passwordEnabled - false - - - - - id - gettemplateid3 - - - - - - startVirtualMachine - [45] startVirtualMachine-TestCase - - - id - getvmid1 - - - - - - rebootVirtualMachine - [46] rebootVirtualMachine-TestCase - - - id - getvmid1 - - - - - - createSnapshotPolicy - [47] createHOURLYSnapshotPolicyForRootVolume-TestCase - - - intervaltype - hourly - - - schedule - 11 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [48] createDAILYSnapshotPolicy-TestCase - - - intervaltype - daily - - - schedule - 13:07 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [49] createWEEKLYSnapshotPolicy-TestCase - - - intervaltype - weekly - - - schedule - 16:07:4 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [50] createMONTHLYSnapshotPolicy-TestCase - - - intervaltype - monthly - - - schedule - 18:19:1 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - id - getmonthlysnapshotpolicyid1 - - - - - - deleteSnapshotPolicies - [51] deleteMONTHLYSnapshotPolicies-TestCase - - - id - getmonthlysnapshotpolicyid1 - - - - - - deleteVolume - [52] deleteVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - deleteVolume - [53] deleteVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - - - destroyVirtualMachine - [54] destroyVirtualMachine-TestCase - - - id - getvmid1 - - - - - - recoverVirtualMachine - [55] recoverVirtualMachine-TestCase - - - id - getvmid1 - - - - - - startVirtualMachine - [56] startVirtualMachine-TestCase - - - id - getvmid1 - - - - - - rebootVirtualMachine - [57] rebootVirtualMachine-TestCase - - - id - getvmid1 - - - - - - stopVirtualMachine - [58] stopVirtualMachine-TestCase - - - id - getvmid1 - - - - - - destroyVirtualMachine - [59] destroyVirtualMachine-TestCase - - - id - getvmid1 - - - - - - recoverVirtualMachine - [60] recoverVirtualMachine-TestCase - - - id - getvmid1 - - - - - - changeServiceForVirtualMachine - [61] changeServiceForVirtualMachine-TestCase - - - id - getvmid1 - - - serviceOfferingId - globalchangeservoff - - - - - - startVirtualMachine - [62] startVirtualMachine-TestCase - - - id - getvmid1 - - - - - - rebootVirtualMachine - [63] rebootVirtualMachine-TestCase - - - id - getvmid1 - - - - - - stopVirtualMachine - [64] stopVirtualMachine-TestCase - - - id - getvmid1 - - - - - - destroyVirtualMachine - [65] destroyVirtualMachine-TestCase - - - id - getvmid1 - - - - - - - - - - - - - - - updateVirtualMachine - [3] updateVirtualMachine-TestCase - - - id - globalisovmid1 - - - displayName - ISO-Third-VM - - - group - ISO-Third-VM - - - haenable - false - - - ostypeid - 112 - - - - - - - - associateIpAddress - [4] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - enableStaticNat - [5] enableStaticNat-TestCase - - - ipaddressid - getpublicipid1 - - - virtualmachineid - globalisovmid1 - - - - - - createIpForwardingRule - [6] createIpForwardingRule-TestCase - - - ipaddressid - getpublicipid1 - - - startPort - 1 - - - endPort - 100 - - - protocol - TCP - - - - - id - getipfwdruleid1 - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getipaddress1 - - - p - password - - - - - - deleteIpForwardingRule - [7] deleteIpForwardingRule-TestCase - - - id - getipfwdruleid1 - - - - - - disableStaticNat - [8] disableStaticNat-TestCase - - - ipaddressid - getpublicipid1 - - - - - - disassociateIpAddress - [9] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [10] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - createPortForwardingRule - [11] createPortForwardingRule-TestCase - - - ipaddressid - getpublicipid1 - - - cidrlist - 0.0.0.0/0 - - - publicport - 22 - - - publicendport - 30 - - - privateport - 22 - - - privateendport - 30 - - - protocol - TCP - - - virtualmachineid - globalisovmid1 - - - - - id - getportfwdruleid1 - - - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh test for the vm - - - h - getipaddress1 - - - p - password - - - - - - deletePortForwardingRule - [12] deletePortForwardingRule-TestCase - - - id - getportfwdruleid1 - - - - - - disassociateIpAddress - [13] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [14] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - ipaddress - getipaddress1 - - - - - - createLoadBalancerRule - [15] createLoadBalancerRule-TestCase - - - publicipid - getpublicipid1 - - - name - LBRule-1 - - - publicport - 80 - - - privateport - 80 - - - algorithm - roundrobin - - - - - id - getldblcrule1 - - - - - - assignToLoadBalancerRule - [16] assignToLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - globalisovmid1 - - - - - - - removeFromLoadBalancerRule - [18] removeFromLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - globalisovmid1 - - - - - - updateLoadBalancerRule - [19] updateLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - name - LBRuleUpdated-1 - - - algorithm - roundrobin - - - - - - disassociateIpAddress - [20] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - associateIpAddress - [14] associateIpAddress-TestCase - - - zoneid - globalzoneid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getpublicipid1 - - - - - - createLoadBalancerRule - [21] createLoadBalancerRule-TestCase - - - publicipid - getpublicipid1 - - - name - LBRule-2 - - - publicport - 80 - - - privateport - 80 - - - algorithm - roundrobin - - - - - id - getldblcrule1 - - - - - - assignToLoadBalancerRule - [22] assignToLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - virtualmachineids - globalisovmid1 - - - - - - - - deleteLoadBalancerRule - [24] deleteLoadBalancerRule-TestCase - - - id - getldblcrule1 - - - - - - disassociateIpAddress - [25] disassociateIpAddress-TestCase - - - id - getpublicipid1 - - - - - - - listVolumes - [26] listROOTVolumes-TestCase - - - virtualmachineid - globalisovmid1 - - - type - ROOT - - - - - id - getrootvolumeid1 - - - - - - createSnapshot - [26] createSnapshotofROOTVolume-TestCase - - - volumeid - getrootvolumeid1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getsnapshotid1 - - - - - - listSnapshots - [27] listROOTVolumeSnapshot-TestCase - - - volumeid - getrootvolumeid1 - - - - - - listVolumes - [26] listDATADISKVolumes-TestCase - - - virtualmachineid - globalisovmid1 - - - type - DATADISK - - - - - id - getdatavolumeid1 - - - - - - createSnapshot - [28] createSnapshotofDATADISKVolume-TestCase - - - volumeid - getdatavolumeid1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getsnapshotid2 - - - - - - listSnapshots - [29] listDATADISKVolumeSnapshot-TestCase - - - volumeid - getdatavolumeid1 - - - - - - createTemplate - [30] createTemplateFromSnapshotTakenofROOTVolume-TestCase - - - snapshotid - getsnapshotid1 - - - name - TemplateFromSnapshot-1 - - - displaytext - TemplateFromSnapshot-1 - - - ostypeid - 12 - - - isPublic - true - - - passwordEnabled - false - - - isfeatured - true - - - - - id - gettemplateid1 - - - - - - createTemplate - [31] createTemplateFromSnapshotTakenofDATADISKVolume-TestCase - - - snapshotid - getsnapshotid2 - - - name - TemplateFromSnapshot-2 - - - displaytext - TemplateFromSnapshot-2 - - - ostypeid - 12 - - - isPublic - true - - - passwordEnabled - false - - - isfeatured - true - - - - - id - gettemplateid2 - - - - - - createVolume - [32] createVolumeFromSnapshotTakenofROOTVolume-TestCase - - - snapshotid - getsnapshotid1 - - - name - VlmfromSnapshot-1 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getdatavolumeid2 - - - - - - createVolume - [33] createVolumeFromSnapshotTakenofDATADISKVolume-TestCase - - - snapshotid - getsnapshotid2 - - - name - VlmfromSnapshot-2 - - - account - accountname - - - domainid - globaldomainid - - - - - id - getdatavolumeid3 - - - - - - - attachVolume - [34] attachVolume1-TestCase - - - id - getdatavolumeid2 - - - virtualMachineId - globalisovmid1 - - - - - - attachVolume - [35] attachVolume2-TestCase - - - id - getdatavolumeid3 - - - virtualMachineId - globalisovmid1 - - - - - - - - detachVolume - [36] detachVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - detachVolume - [37] detachVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - attachVolume - [34] attachVolume1-TestCase - - - id - getdatavolumeid2 - - - virtualMachineId - globalisovmid1 - - - - - - attachVolume - [35] attachVolume2-TestCase - - - id - getdatavolumeid3 - - - virtualMachineId - globalisovmid1 - - - - - - stopVirtualMachine - [40] stopVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - updateVirtualMachine - [41] updateVirtualMachine-TestCase - - - id - globalisovmid1 - - - displayName - First-VM-1-1 - - - group - First-VM-1-1 - - - haenable - false - - - ostypeid - 12 - - - - - - detachVolume - [36] detachVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - detachVolume - [37] detachVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - createTemplate - [44] createTemplateFromROOTVolume-TestCase - - - volumeId - getrootvolumeid1 - - - name - TemplateFromRootVolume-3 - - - displayText - TemplateFromRootVolume-3 - - - osTypeId - 12 - - - isPublic - true - - - passwordEnabled - false - - - - - id - gettemplateid3 - - - - - - startVirtualMachine - [45] startVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - rebootVirtualMachine - [46] rebootVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - createSnapshotPolicy - [47] createHOURLYSnapshotPolicyForRootVolume-TestCase - - - intervaltype - hourly - - - schedule - 11 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [48] createDAILYSnapshotPolicy-TestCase - - - intervaltype - daily - - - schedule - 13:07 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [49] createWEEKLYSnapshotPolicy-TestCase - - - intervaltype - weekly - - - schedule - 16:07:4 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - - createSnapshotPolicy - [50] createMONTHLYSnapshotPolicy-TestCase - - - intervaltype - monthly - - - schedule - 18:19:1 - - - volumeid - getrootvolumeid1 - - - maxsnaps - 8 - - - timezone - America%2FLos_Angeles - - - - - id - getmonthlysnapshotpolicyid1 - - - - - - deleteSnapshotPolicies - [51] deleteMONTHLYSnapshotPolicies-TestCase - - - id - getmonthlysnapshotpolicyid1 - - - - - - deleteVolume - [52] deleteVolume1-TestCase - - - id - getdatavolumeid2 - - - - - - deleteVolume - [53] deleteVolume2-TestCase - - - id - getdatavolumeid3 - - - - - - - - destroyVirtualMachine - [54] destroyVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - recoverVirtualMachine - [55] recoverVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - startVirtualMachine - [56] startVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - rebootVirtualMachine - [57] rebootVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - stopVirtualMachine - [58] stopVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - destroyVirtualMachine - [59] destroyVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - recoverVirtualMachine - [60] recoverVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - changeServiceForVirtualMachine - [61] changeServiceForVirtualMachine-TestCase - - - id - globalisovmid1 - - - serviceOfferingId - globalchangeservoff - - - - - - startVirtualMachine - [62] startVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - rebootVirtualMachine - [63] rebootVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - stopVirtualMachine - [64] stopVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - destroyVirtualMachine - [65] destroyVirtualMachine-TestCase - - - id - globalisovmid1 - - - - - - deployVirtualMachine - [66] deployVirtualMachineFromTemplateCreatedFromSnapshotofROOTVolume-TestCase - - - zoneId - globalzoneid - - - hypervisor - globalhypervisortype - - - templateId - gettemplateid1 - - - serviceOfferingId - servofferid - - - networkIds - getnetworkid1 - - - diskOfferingId - diskofferid - - - account - accountname - - - domainid - globaldomainid - - - - - id - getvmid1 - - - - - - deleteTemplate - [67] deleteTemplateofSnapshotTakenofROOTVolume-TestCase - - - id - gettemplateid1 - - - zoneid - globalzoneid - - - - - - deleteTemplate - [68] deleteTemplateofSnapshotTakenofDATAVolume-TestCase - - - id - gettemplateid2 - - - zoneid - globalzoneid - - - - - - deleteTemplate - [69] deleteTemplateCreatedFromROOTVolume-TestCase - - - id - gettemplateid3 - - - zoneid - globalzoneid - - - - - - deleteSnapshot - [70] deleteSnapshot-TestCase - - - id - getsnapshotid1 - - - - - - deleteSnapshot - [71] deleteSnapshot-TestCase - - - id - getsnapshotid2 - - - - - - - - - - extractIso - [] extractIsoTestCase - - - id - getisoid1 - - - zoneid - 1 - - - mode - HTTP_DOWNLOAD - - - - - - deleteIso - [] deleteIsoTestCase - - - id - getisoid1 - - - zoneid - 1 - - - - - - deleteAccount - [72] deleteAccount-TestCase - - - id - getaccountid - - - - + + + + listDomains + [List Domains] + + + id + globaldomainid + + + + + name + getdomainname1 + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + email + firstuser@gmail.com + + + firstname + firstuser + + + lastname + firstuserlastname + + + username + getusernameparam1 + + + password + password + + + + + id + getaccountid + + + state + enabled + + + + + + registerIso + [] registerIsotestcase + + + name + CentOS-5-5-ISO + + + displayText + CentOS-5-5-ISO + + + url + http://nfs1.lab.vmops.com/isos_64bit/CentOS-5.5-x86_64-bin-DVDs/CentOS-5.5-x86_64-bin-DVD-1of2.iso + + + zoneId + -1 + + + isPublic + true + + + bootable + true + + + osTypeId + 112 + + + isfeatured + true + + + account + accountname + + + domainid + globaldomainid + + + + + id + getisoid1 + + + + + + listIsos + [] listIsosTestCase + + + isofilter + self + + + id + getisoid1 + + + zoneid + 1 + + + + + id + getisoid1 + + + name + CentOS-5-5-ISO + + + displayText + CentOS-5-5-ISO + + + zoneId + 1 + + + isPublic + true + + + bootable + true + + + osTypeId + 112 + + + isfeatured + true + + + account + accountname + + + domainid + globaldomainid + + + domain + getdomainname1 + + + + + + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + globaldomainid + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + getnetworkid1 + + + + + + + + listAccounts + 950 [List the Account of the User created in (3)] + + + name + accountname + + + + + id + getaccountid + + + name + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + id + getuserid1 + + + username + getusernameparam1 + + + + + + + createUser + 3 [Create User Command] + + + account + accountname + + + accounttype + globalaccttype + + + username + getusernameparam2 + + + password + password + + + firstname + seconduser + + + lastname + seconduserlastname + + + email + seconduser@gmail.com + + + domainid + globaldomainid + + + + + id + getuserid2 + + + + + + + + updateUser + 6 [Update the User created in (3)] + + + id + getuserid1 + + + username + getusernameparam3 + + + password + password + + + firstname + updatedfirstuser + + + lastname + updatedfirstuserlastname + + + email + updatedfirstuser@gmail.com + + + timezone + PST + + + + + id + getuserid1 + + + username + getusernameparam3 + + + + + + + listUsers + 5 [List the Updated User updated in (6)] + + + id + getuserid1 + + + + + id + getuserid1 + + + username + getusernameparam3 + + + account + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + timezone + PST + + + + + + + disableUser + 428 [Disable the User created in (3)] + + + id + getuserid1 + + + + + id + getuserid1 + + + username + getusernameparam3 + + + firstname + updatedfirstuser + + + lastname + updatedfirstuserlastname + + + email + updatedfirstuser@gmail.com + + + state + disabled + + + account + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + timezone + PST + + + + + + + enableUser + 9 [Enable the User created in (3)] + + + id + getuserid1 + + + + + id + getuserid1 + + + username + getusernameparam3 + + + firstname + updatednimbus + + + lastname + updatednimbuslastname + + + email + updatednimbus@gmail.com + + + created + createtimestamp + + + state + enabled + + + account + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + timezone + PST + + + + + + + disableAccount + [Disable the Account] + + + account + accountname + + + domainid + globaldomainid + + + lock + true + + + + + id + getaccountid + + + name + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + locked + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + globaldomainid + + + + + id + getaccountid + + + name + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + disableAccount + [Lock the Account] + + + account + accountname + + + domainid + globaldomainid + + + lock + false + + + + + id + getaccountid + + + name + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + getdomainname1 + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + disabled + + + + + + + enableAccount + [Enable the Locked Account] + + + account + accountname + + + domainid + globaldomainid + + + + + id + getaccountid + + + name + accountname + + + accounttype + globalaccttype + + + domainid + globaldomainid + + + domain + ROOT + + + receivedbytes + 0 + + + sentbytes + 0 + + + vmlimit + Unlimited + + + vmtotal + 0 + + + vmavailable + Unlimited + + + ipavailable + Unlimited + + + iptotal + 0 + + + iplimit + Unlimited + + + volumelimit + Unlimited + + + volumetotal + 0 + + + volumeavailable + Unlimited + + + snapshotlimit + Unlimited + + + snapshottotal + 0 + + + snapshotavailable + Unlimited + + + templatelimit + Unlimited + + + templatetotal + 0 + + + templateavailable + Unlimited + + + vmstopped + 0 + + + vmrunning + 0 + + + state + enabled + + + + + + + + createDomain + [Create Domain] + + + name + getdomainname2 + + + parentdomainid + globaldomainid + + + + + id + getdomainid1 + + + + + + + listDomains + 11 [List Domains] + + + name + getdomainname2 + + + + + + id + getdomainid1 + + + name + getdomainname2 + + + level + getcurrentlevel1 + + + parentdomainid + globaldomainid + + + parentdomainname + getdomainname1 + + + haschild + false + + + + + + + updateDomain + 952 [Update the Domain created in (10)] + + + id + getdomainid1 + + + name + getdomainname2 + + + + + id + getdomainid1 + + + name + getdomainname2 + + + level + getcurrentlevel1 + + + parentdomainid + globaldomainid + + + parentdomainname + getdomainname1 + + + haschild + false + + + + + + + + listDomainChildren + - [List One of the Children of the Parent Domain] + + + id + globaldomainid + + + + + domain + 0 + + id + getdomainid1 + + + name + getdomainname2 + + + level + getcurrentlevel1 + + + parentdomainid + globaldomainid + + + parentdomainname + getdomainname1 + + + haschild + false + + + + + + + deleteDomain + 654 [Delete Domain created in (10)] + + + id + getdomainid1 + + + cleanup + true + + + + + + success + true + + + + + + + listTemplates + 27 [List Templates - List System Template] + + + templatefilter + self + + + id + 1 + + + + + template + 0 + + id + debian50templateid + + + name + debian50templatename + + + displaytext + debian50templatedisplaytext + + + ispublic + debian50templateispublic + + + created + debian50templatecreated + + + isready + true + + + passwordenabled + debian50templatepasswordenabled + + + format + VHD + + + isfeatured + false + + + crossZones + true + + + ostypeid + debian50templateostypeid + + + ostypename + debian50templateostypename + + + size + debian50templatesize + + + account + system + + + zoneid + globalzoneid + + + zonename + globalzonename + + + status + Download Complete + + + templatetype + SYSTEM + + + hypervisor + XenServer + + + domain + ROOT + + + domainid + 1 + + + + + + + + + listServiceOfferings + 28 [List the Small Instance Service Offering] + + + id + globalserviceofferingid + + + + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + listServiceOfferings + 876 [Verify the First Service Offering] + + + name + smallofferingname + + + + serviceoffering + 0 + + id + smallofferingid + + + name + smallofferingname + + + displaytext + smallofferingdisplaytext + + + cpunumber + smallofferingcpunumber + + + cpuspeed + smallofferingcpuspeed + + + memory + smallofferingmemory + + + storagetype + smallofferingstoragetype + + + offerha + false + + + + + + + + listZones + 39 [List the Global Zone with id-2] + + zone + 0 + + id + globalzoneid + + + name + globalzonename + + + dns1 + dns1 + + + dns2 + dns2 + + + internaldns1 + internaldns1 + + + vlan + + + guestcidraddress + + + networktype + Advanced + + + + + + + + + + createServiceOffering + [1] createServiceOffering-TestCase + + + issystem + false + + + name + LittleInstance + + + displaytext + LittleInstance + + + storagetype + shared + + + cpuNumber + 1 + + + cpuSpeed + 100 + + + memory + 128 + + + offerha + false + + + limitcpuuse + false + + + + + id + servofferid + + + + + + + +createDiskOffering + [1] createDiskOffering-TestCase + + + isMirrored + false + + + name + Little + + + displaytext + LittleDisk1GB + + + customized + false + + + disksize + 1 + + + + + id + diskofferid + + + + + + + + deployVirtualMachine + [2] deployVirtualMachine-TestCase + + + zoneId + globalzoneid + + + hypervisor + globalhypervisortype + + + templateId + globaltemplateid + + + serviceOfferingId + servofferid + + + networkIds + getnetworkid1 + + + diskOfferingId + diskofferid + + + displayname + FirstVM + + + group + FirstVM + + + account + accountname + + + domainid + globaldomainid + + + + + id + getvmid1 + + + + + + updateVirtualMachine + [3] updateVirtualMachine-TestCase + + + id + getvmid1 + + + displayName + First-VM + + + group + First-VM + + + haenable + false + + + ostypeid + 12 + + + + + + + + associateIpAddress + [4] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + enableStaticNat + [5] enableStaticNat-TestCase + + + ipaddressid + getpublicipid1 + + + virtualmachineid + getvmid1 + + + + + + createIpForwardingRule + [6] createIpForwardingRule-TestCase + + + ipaddressid + getpublicipid1 + + + startPort + 1 + + + endPort + 100 + + + protocol + TCP + + + + + id + getipfwdruleid1 + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getipaddress1 + + + p + password + + + + + + deleteIpForwardingRule + [7] deleteIpForwardingRule-TestCase + + + id + getipfwdruleid1 + + + + + + disableStaticNat + [8] disableStaticNat-TestCase + + + ipaddressid + getpublicipid1 + + + + + + disassociateIpAddress + [9] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [10] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + createPortForwardingRule + [11] createPortForwardingRule-TestCase + + + ipaddressid + getpublicipid1 + + + cidrlist + 0.0.0.0/0 + + + publicport + 22 + + + publicendport + 30 + + + privateport + 22 + + + privateendport + 30 + + + protocol + TCP + + + virtualmachineid + getvmid1 + + + + + id + getportfwdruleid1 + + + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getipaddress1 + + + p + password + + + + + + deletePortForwardingRule + [12] deletePortForwardingRule-TestCase + + + id + getportfwdruleid1 + + + + + + disassociateIpAddress + [13] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [14] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + createLoadBalancerRule + [15] createLoadBalancerRule-TestCase + + + publicipid + getpublicipid1 + + + name + LBRule-1 + + + publicport + 80 + + + privateport + 80 + + + algorithm + roundrobin + + + + + id + getldblcrule1 + + + + + + assignToLoadBalancerRule + [16] assignToLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + getvmid1 + + + + + + + removeFromLoadBalancerRule + [18] removeFromLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + getvmid1 + + + + + + updateLoadBalancerRule + [19] updateLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + name + LBRuleUpdated-1 + + + algorithm + roundrobin + + + + + + disassociateIpAddress + [20] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [14] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + + + + createLoadBalancerRule + [21] createLoadBalancerRule-TestCase + + + publicipid + getpublicipid1 + + + name + LBRule-2 + + + publicport + 80 + + + privateport + 80 + + + algorithm + roundrobin + + + + + id + getldblcrule1 + + + + + + assignToLoadBalancerRule + [22] assignToLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + getvmid1 + + + + + + + + deleteLoadBalancerRule + [24] deleteLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + + + + disassociateIpAddress + [25] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + + listVolumes + [26] listROOTVolumes-TestCase + + + virtualmachineid + getvmid1 + + + type + ROOT + + + + + id + getrootvolumeid1 + + + + + + createSnapshot + [26] createSnapshotofROOTVolume-TestCase + + + volumeid + getrootvolumeid1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getsnapshotid1 + + + + + + listSnapshots + [27] listROOTVolumeSnapshot-TestCase + + + volumeid + getrootvolumeid1 + + + + + + listVolumes + [26] listDATADISKVolumes-TestCase + + + virtualmachineid + getvmid1 + + + type + DATADISK + + + + + id + getdatavolumeid1 + + + + + + createSnapshot + [28] createSnapshotofDATADISKVolume-TestCase + + + volumeid + getdatavolumeid1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getsnapshotid2 + + + + + + listSnapshots + [29] listDATADISKVolumeSnapshot-TestCase + + + volumeid + getdatavolumeid1 + + + + + + createTemplate + [30] createTemplateFromSnapshotTakenofROOTVolume-TestCase + + + snapshotid + getsnapshotid1 + + + name + TemplateFromSnapshot-1 + + + displaytext + TemplateFromSnapshot-1 + + + ostypeid + 12 + + + isPublic + true + + + passwordEnabled + false + + + isfeatured + true + + + + + id + gettemplateid1 + + + + + + createTemplate + [31] createTemplateFromSnapshotTakenofDATADISKVolume-TestCase + + + snapshotid + getsnapshotid2 + + + name + TemplateFromSnapshot-2 + + + displaytext + TemplateFromSnapshot-2 + + + ostypeid + 12 + + + isPublic + true + + + passwordEnabled + false + + + isfeatured + true + + + + + id + gettemplateid2 + + + + + + createVolume + [32] createVolumeFromSnapshotTakenofROOTVolume-TestCase + + + snapshotid + getsnapshotid1 + + + name + VlmfromSnapshot-1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getdatavolumeid2 + + + + + + createVolume + [33] createVolumeFromSnapshotTakenofDATADISKVolume-TestCase + + + snapshotid + getsnapshotid2 + + + name + VlmfromSnapshot-2 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getdatavolumeid3 + + + + + + + attachVolume + [34] attachVolume1-TestCase + + + id + getdatavolumeid2 + + + virtualMachineId + getvmid1 + + + + + + attachVolume + [35] attachVolume2-TestCase + + + id + getdatavolumeid3 + + + virtualMachineId + getvmid1 + + + + + + + + detachVolume + [36] detachVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + detachVolume + [37] detachVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + attachVolume + [34] attachVolume1-TestCase + + + id + getdatavolumeid2 + + + virtualMachineId + getvmid1 + + + + + + attachVolume + [35] attachVolume2-TestCase + + + id + getdatavolumeid3 + + + virtualMachineId + getvmid1 + + + + + + stopVirtualMachine + [40] stopVirtualMachine-TestCase + + + id + getvmid1 + + + + + + updateVirtualMachine + [41] updateVirtualMachine-TestCase + + + id + getvmid1 + + + displayName + First-VM-1-1 + + + group + First-VM-1-1 + + + haenable + false + + + ostypeid + 12 + + + + + + detachVolume + [36] detachVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + detachVolume + [37] detachVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + createTemplate + [44] createTemplateFromROOTVolume-TestCase + + + volumeId + getrootvolumeid1 + + + name + TemplateFromRootVolume-3 + + + displayText + TemplateFromRootVolume-3 + + + osTypeId + 12 + + + isPublic + true + + + passwordEnabled + false + + + + + id + gettemplateid3 + + + + + + startVirtualMachine + [45] startVirtualMachine-TestCase + + + id + getvmid1 + + + + + + rebootVirtualMachine + [46] rebootVirtualMachine-TestCase + + + id + getvmid1 + + + + + + createSnapshotPolicy + [47] createHOURLYSnapshotPolicyForRootVolume-TestCase + + + intervaltype + hourly + + + schedule + 11 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [48] createDAILYSnapshotPolicy-TestCase + + + intervaltype + daily + + + schedule + 13:07 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [49] createWEEKLYSnapshotPolicy-TestCase + + + intervaltype + weekly + + + schedule + 16:07:4 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [50] createMONTHLYSnapshotPolicy-TestCase + + + intervaltype + monthly + + + schedule + 18:19:1 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + id + getmonthlysnapshotpolicyid1 + + + + + + deleteSnapshotPolicies + [51] deleteMONTHLYSnapshotPolicies-TestCase + + + id + getmonthlysnapshotpolicyid1 + + + + + + deleteVolume + [52] deleteVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + deleteVolume + [53] deleteVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + + + destroyVirtualMachine + [54] destroyVirtualMachine-TestCase + + + id + getvmid1 + + + + + + recoverVirtualMachine + [55] recoverVirtualMachine-TestCase + + + id + getvmid1 + + + + + + startVirtualMachine + [56] startVirtualMachine-TestCase + + + id + getvmid1 + + + + + + rebootVirtualMachine + [57] rebootVirtualMachine-TestCase + + + id + getvmid1 + + + + + + stopVirtualMachine + [58] stopVirtualMachine-TestCase + + + id + getvmid1 + + + + + + destroyVirtualMachine + [59] destroyVirtualMachine-TestCase + + + id + getvmid1 + + + + + + recoverVirtualMachine + [60] recoverVirtualMachine-TestCase + + + id + getvmid1 + + + + + + changeServiceForVirtualMachine + [61] changeServiceForVirtualMachine-TestCase + + + id + getvmid1 + + + serviceOfferingId + globalchangeservoff + + + + + + startVirtualMachine + [62] startVirtualMachine-TestCase + + + id + getvmid1 + + + + + + rebootVirtualMachine + [63] rebootVirtualMachine-TestCase + + + id + getvmid1 + + + + + + stopVirtualMachine + [64] stopVirtualMachine-TestCase + + + id + getvmid1 + + + + + + destroyVirtualMachine + [65] destroyVirtualMachine-TestCase + + + id + getvmid1 + + + + + + deployVirtualMachine + [66] deployVirtualMachineFromTemplateCreatedFromSnapshotofROOTVolume-TestCase + + + zoneId + globalzoneid + + + hypervisor + globalhypervisortype + + + templateId + gettemplateid1 + + + serviceOfferingId + servofferid + + + networkIds + getnetworkid1 + + + diskOfferingId + diskofferid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getvmid1 + + + + + + deleteTemplate + [67] deleteTemplateofSnapshotTakenofROOTVolume-TestCase + + + id + gettemplateid1 + + + zoneid + globalzoneid + + + + + + deleteTemplate + [68] deleteTemplateofSnapshotTakenofDATAVolume-TestCase + + + id + gettemplateid2 + + + zoneid + globalzoneid + + + + + + deleteTemplate + [69] deleteTemplateCreatedFromROOTVolume-TestCase + + + id + gettemplateid3 + + + zoneid + globalzoneid + + + + + + deleteSnapshot + [70] deleteSnapshot-TestCase + + + id + getsnapshotid1 + + + + + + deleteSnapshot + [71] deleteSnapshot-TestCase + + + id + getsnapshotid2 + + + + + + + + + updateVirtualMachine + [3] updateVirtualMachine-TestCase + + + id + getvmid1 + + + displayName + First-VM + + + group + First-VM + + + haenable + false + + + ostypeid + 12 + + + + + + + + associateIpAddress + [4] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + enableStaticNat + [5] enableStaticNat-TestCase + + + ipaddressid + getpublicipid1 + + + virtualmachineid + getvmid1 + + + + + + createIpForwardingRule + [6] createIpForwardingRule-TestCase + + + ipaddressid + getpublicipid1 + + + startPort + 1 + + + endPort + 100 + + + protocol + TCP + + + + + id + getipfwdruleid1 + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getipaddress1 + + + p + password + + + + + + deleteIpForwardingRule + [7] deleteIpForwardingRule-TestCase + + + id + getipfwdruleid1 + + + + + + disableStaticNat + [8] disableStaticNat-TestCase + + + ipaddressid + getpublicipid1 + + + + + + disassociateIpAddress + [9] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [10] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + createPortForwardingRule + [11] createPortForwardingRule-TestCase + + + ipaddressid + getpublicipid1 + + + cidrlist + 0.0.0.0/0 + + + publicport + 22 + + + publicendport + 30 + + + privateport + 22 + + + privateendport + 30 + + + protocol + TCP + + + virtualmachineid + getvmid1 + + + + + id + getportfwdruleid1 + + + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getipaddress1 + + + p + password + + + + + + deletePortForwardingRule + [12] deletePortForwardingRule-TestCase + + + id + getportfwdruleid1 + + + + + + disassociateIpAddress + [13] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [14] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + createLoadBalancerRule + [15] createLoadBalancerRule-TestCase + + + publicipid + getpublicipid1 + + + name + LBRule-1 + + + publicport + 80 + + + privateport + 80 + + + algorithm + roundrobin + + + + + id + getldblcrule1 + + + + + + assignToLoadBalancerRule + [16] assignToLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + getvmid1 + + + + + + + removeFromLoadBalancerRule + [18] removeFromLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + getvmid1 + + + + + + updateLoadBalancerRule + [19] updateLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + name + LBRuleUpdated-1 + + + algorithm + roundrobin + + + + + + disassociateIpAddress + [20] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [14] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + + + + createLoadBalancerRule + [21] createLoadBalancerRule-TestCase + + + publicipid + getpublicipid1 + + + name + LBRule-2 + + + publicport + 80 + + + privateport + 80 + + + algorithm + roundrobin + + + + + id + getldblcrule1 + + + + + + assignToLoadBalancerRule + [22] assignToLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + getvmid1 + + + + + + + deleteLoadBalancerRule + [24] deleteLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + + + + disassociateIpAddress + [25] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + + listVolumes + [26] listROOTVolumes-TestCase + + + virtualmachineid + getvmid1 + + + type + ROOT + + + + + id + getrootvolumeid1 + + + + + + createSnapshot + [26] createSnapshotofROOTVolume-TestCase + + + volumeid + getrootvolumeid1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getsnapshotid1 + + + + + + listSnapshots + [27] listROOTVolumeSnapshot-TestCase + + + volumeid + getrootvolumeid1 + + + + + + listVolumes + [26] listDATADISKVolumes-TestCase + + + virtualmachineid + getvmid1 + + + type + DATADISK + + + + + id + getdatavolumeid1 + + + + + + createSnapshot + [28] createSnapshotofDATADISKVolume-TestCase + + + volumeid + getdatavolumeid1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getsnapshotid2 + + + + + + listSnapshots + [29] listDATADISKVolumeSnapshot-TestCase + + + volumeid + getdatavolumeid1 + + + + + + createTemplate + [30] createTemplateFromSnapshotTakenofROOTVolume-TestCase + + + snapshotid + getsnapshotid1 + + + name + TemplateFromSnapshot-4 + + + displaytext + TemplateFromSnapshot-4 + + + ostypeid + 12 + + + isPublic + true + + + passwordEnabled + false + + + isfeatured + true + + + + + id + gettemplateid1 + + + + + + createTemplate + [31] createTemplateFromSnapshotTakenofDATADISKVolume-TestCase + + + snapshotid + getsnapshotid2 + + + name + TemplateFromSnapshot-5 + + + displaytext + TemplateFromSnapshot-5 + + + ostypeid + 12 + + + isPublic + true + + + passwordEnabled + false + + + isfeatured + true + + + + + id + gettemplateid2 + + + + + + createVolume + [32] createVolumeFromSnapshotTakenofROOTVolume-TestCase + + + snapshotid + getsnapshotid1 + + + name + VlmfromSnapshot-1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getdatavolumeid2 + + + + + + createVolume + [33] createVolumeFromSnapshotTakenofDATADISKVolume-TestCase + + + snapshotid + getsnapshotid2 + + + name + VlmfromSnapshot-2 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getdatavolumeid3 + + + + + + + attachVolume + [34] attachVolume1-TestCase + + + id + getdatavolumeid2 + + + virtualMachineId + getvmid1 + + + + + + attachVolume + [35] attachVolume2-TestCase + + + id + getdatavolumeid3 + + + virtualMachineId + getvmid1 + + + + + + + + detachVolume + [36] detachVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + detachVolume + [37] detachVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + attachVolume + [34] attachVolume1-TestCase + + + id + getdatavolumeid2 + + + virtualMachineId + getvmid1 + + + + + + attachVolume + [35] attachVolume2-TestCase + + + id + getdatavolumeid3 + + + virtualMachineId + getvmid1 + + + + + + stopVirtualMachine + [40] stopVirtualMachine-TestCase + + + id + getvmid1 + + + + + + updateVirtualMachine + [41] updateVirtualMachine-TestCase + + + id + getvmid1 + + + displayName + First-VM-1-1 + + + group + First-VM-1-1 + + + haenable + false + + + ostypeid + 12 + + + + + + detachVolume + [36] detachVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + detachVolume + [37] detachVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + createTemplate + [44] createTemplateFromROOTVolume-TestCase + + + volumeId + getrootvolumeid1 + + + name + TemplateFromRootVolume-6 + + + displayText + TemplateFromRootVolume-6 + + + osTypeId + 12 + + + isPublic + true + + + passwordEnabled + false + + + + + id + gettemplateid3 + + + + + + startVirtualMachine + [45] startVirtualMachine-TestCase + + + id + getvmid1 + + + + + + rebootVirtualMachine + [46] rebootVirtualMachine-TestCase + + + id + getvmid1 + + + + + + createSnapshotPolicy + [47] createHOURLYSnapshotPolicyForRootVolume-TestCase + + + intervaltype + hourly + + + schedule + 11 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [48] createDAILYSnapshotPolicy-TestCase + + + intervaltype + daily + + + schedule + 13:07 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [49] createWEEKLYSnapshotPolicy-TestCase + + + intervaltype + weekly + + + schedule + 16:07:4 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [50] createMONTHLYSnapshotPolicy-TestCase + + + intervaltype + monthly + + + schedule + 18:19:1 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + id + getmonthlysnapshotpolicyid1 + + + + + + deleteSnapshotPolicies + [51] deleteMONTHLYSnapshotPolicies-TestCase + + + id + getmonthlysnapshotpolicyid1 + + + + + + deleteVolume + [52] deleteVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + deleteVolume + [53] deleteVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + + + destroyVirtualMachine + [54] destroyVirtualMachine-TestCase + + + id + getvmid1 + + + + + + recoverVirtualMachine + [55] recoverVirtualMachine-TestCase + + + id + getvmid1 + + + + + + startVirtualMachine + [56] startVirtualMachine-TestCase + + + id + getvmid1 + + + + + + rebootVirtualMachine + [57] rebootVirtualMachine-TestCase + + + id + getvmid1 + + + + + + stopVirtualMachine + [58] stopVirtualMachine-TestCase + + + id + getvmid1 + + + + + + destroyVirtualMachine + [59] destroyVirtualMachine-TestCase + + + id + getvmid1 + + + + + + recoverVirtualMachine + [60] recoverVirtualMachine-TestCase + + + id + getvmid1 + + + + + + changeServiceForVirtualMachine + [61] changeServiceForVirtualMachine-TestCase + + + id + getvmid1 + + + serviceOfferingId + globalchangeservoff + + + + + + startVirtualMachine + [62] startVirtualMachine-TestCase + + + id + getvmid1 + + + + + + rebootVirtualMachine + [63] rebootVirtualMachine-TestCase + + + id + getvmid1 + + + + + + stopVirtualMachine + [64] stopVirtualMachine-TestCase + + + id + getvmid1 + + + + + + destroyVirtualMachine + [65] destroyVirtualMachine-TestCase + + + id + getvmid1 + + + + + + + + + + + + + + + updateVirtualMachine + [3] updateVirtualMachine-TestCase + + + id + globalisovmid1 + + + displayName + ISO-Third-VM + + + group + ISO-Third-VM + + + haenable + false + + + ostypeid + 112 + + + + + + + + associateIpAddress + [4] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + enableStaticNat + [5] enableStaticNat-TestCase + + + ipaddressid + getpublicipid1 + + + virtualmachineid + globalisovmid1 + + + + + + createIpForwardingRule + [6] createIpForwardingRule-TestCase + + + ipaddressid + getpublicipid1 + + + startPort + 1 + + + endPort + 100 + + + protocol + TCP + + + + + id + getipfwdruleid1 + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getipaddress1 + + + p + password + + + + + + deleteIpForwardingRule + [7] deleteIpForwardingRule-TestCase + + + id + getipfwdruleid1 + + + + + + disableStaticNat + [8] disableStaticNat-TestCase + + + ipaddressid + getpublicipid1 + + + + + + disassociateIpAddress + [9] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [10] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + createPortForwardingRule + [11] createPortForwardingRule-TestCase + + + ipaddressid + getpublicipid1 + + + cidrlist + 0.0.0.0/0 + + + publicport + 22 + + + publicendport + 30 + + + privateport + 22 + + + privateendport + 30 + + + protocol + TCP + + + virtualmachineid + globalisovmid1 + + + + + id + getportfwdruleid1 + + + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh test for the vm + + + h + getipaddress1 + + + p + password + + + + + + deletePortForwardingRule + [12] deletePortForwardingRule-TestCase + + + id + getportfwdruleid1 + + + + + + disassociateIpAddress + [13] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [14] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + ipaddress + getipaddress1 + + + + + + createLoadBalancerRule + [15] createLoadBalancerRule-TestCase + + + publicipid + getpublicipid1 + + + name + LBRule-1 + + + publicport + 80 + + + privateport + 80 + + + algorithm + roundrobin + + + + + id + getldblcrule1 + + + + + + assignToLoadBalancerRule + [16] assignToLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + globalisovmid1 + + + + + + + removeFromLoadBalancerRule + [18] removeFromLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + globalisovmid1 + + + + + + updateLoadBalancerRule + [19] updateLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + name + LBRuleUpdated-1 + + + algorithm + roundrobin + + + + + + disassociateIpAddress + [20] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + associateIpAddress + [14] associateIpAddress-TestCase + + + zoneid + globalzoneid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getpublicipid1 + + + + + + createLoadBalancerRule + [21] createLoadBalancerRule-TestCase + + + publicipid + getpublicipid1 + + + name + LBRule-2 + + + publicport + 80 + + + privateport + 80 + + + algorithm + roundrobin + + + + + id + getldblcrule1 + + + + + + assignToLoadBalancerRule + [22] assignToLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + virtualmachineids + globalisovmid1 + + + + + + + + deleteLoadBalancerRule + [24] deleteLoadBalancerRule-TestCase + + + id + getldblcrule1 + + + + + + disassociateIpAddress + [25] disassociateIpAddress-TestCase + + + id + getpublicipid1 + + + + + + + listVolumes + [26] listROOTVolumes-TestCase + + + virtualmachineid + globalisovmid1 + + + type + ROOT + + + + + id + getrootvolumeid1 + + + + + + createSnapshot + [26] createSnapshotofROOTVolume-TestCase + + + volumeid + getrootvolumeid1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getsnapshotid1 + + + + + + listSnapshots + [27] listROOTVolumeSnapshot-TestCase + + + volumeid + getrootvolumeid1 + + + + + + listVolumes + [26] listDATADISKVolumes-TestCase + + + virtualmachineid + globalisovmid1 + + + type + DATADISK + + + + + id + getdatavolumeid1 + + + + + + createSnapshot + [28] createSnapshotofDATADISKVolume-TestCase + + + volumeid + getdatavolumeid1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getsnapshotid2 + + + + + + listSnapshots + [29] listDATADISKVolumeSnapshot-TestCase + + + volumeid + getdatavolumeid1 + + + + + + createTemplate + [30] createTemplateFromSnapshotTakenofROOTVolume-TestCase + + + snapshotid + getsnapshotid1 + + + name + TemplateFromSnapshot-1 + + + displaytext + TemplateFromSnapshot-1 + + + ostypeid + 12 + + + isPublic + true + + + passwordEnabled + false + + + isfeatured + true + + + + + id + gettemplateid1 + + + + + + createTemplate + [31] createTemplateFromSnapshotTakenofDATADISKVolume-TestCase + + + snapshotid + getsnapshotid2 + + + name + TemplateFromSnapshot-2 + + + displaytext + TemplateFromSnapshot-2 + + + ostypeid + 12 + + + isPublic + true + + + passwordEnabled + false + + + isfeatured + true + + + + + id + gettemplateid2 + + + + + + createVolume + [32] createVolumeFromSnapshotTakenofROOTVolume-TestCase + + + snapshotid + getsnapshotid1 + + + name + VlmfromSnapshot-1 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getdatavolumeid2 + + + + + + createVolume + [33] createVolumeFromSnapshotTakenofDATADISKVolume-TestCase + + + snapshotid + getsnapshotid2 + + + name + VlmfromSnapshot-2 + + + account + accountname + + + domainid + globaldomainid + + + + + id + getdatavolumeid3 + + + + + + + attachVolume + [34] attachVolume1-TestCase + + + id + getdatavolumeid2 + + + virtualMachineId + globalisovmid1 + + + + + + attachVolume + [35] attachVolume2-TestCase + + + id + getdatavolumeid3 + + + virtualMachineId + globalisovmid1 + + + + + + + + detachVolume + [36] detachVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + detachVolume + [37] detachVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + attachVolume + [34] attachVolume1-TestCase + + + id + getdatavolumeid2 + + + virtualMachineId + globalisovmid1 + + + + + + attachVolume + [35] attachVolume2-TestCase + + + id + getdatavolumeid3 + + + virtualMachineId + globalisovmid1 + + + + + + stopVirtualMachine + [40] stopVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + updateVirtualMachine + [41] updateVirtualMachine-TestCase + + + id + globalisovmid1 + + + displayName + First-VM-1-1 + + + group + First-VM-1-1 + + + haenable + false + + + ostypeid + 12 + + + + + + detachVolume + [36] detachVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + detachVolume + [37] detachVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + createTemplate + [44] createTemplateFromROOTVolume-TestCase + + + volumeId + getrootvolumeid1 + + + name + TemplateFromRootVolume-3 + + + displayText + TemplateFromRootVolume-3 + + + osTypeId + 12 + + + isPublic + true + + + passwordEnabled + false + + + + + id + gettemplateid3 + + + + + + startVirtualMachine + [45] startVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + rebootVirtualMachine + [46] rebootVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + createSnapshotPolicy + [47] createHOURLYSnapshotPolicyForRootVolume-TestCase + + + intervaltype + hourly + + + schedule + 11 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [48] createDAILYSnapshotPolicy-TestCase + + + intervaltype + daily + + + schedule + 13:07 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [49] createWEEKLYSnapshotPolicy-TestCase + + + intervaltype + weekly + + + schedule + 16:07:4 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + + createSnapshotPolicy + [50] createMONTHLYSnapshotPolicy-TestCase + + + intervaltype + monthly + + + schedule + 18:19:1 + + + volumeid + getrootvolumeid1 + + + maxsnaps + 8 + + + timezone + America%2FLos_Angeles + + + + + id + getmonthlysnapshotpolicyid1 + + + + + + deleteSnapshotPolicies + [51] deleteMONTHLYSnapshotPolicies-TestCase + + + id + getmonthlysnapshotpolicyid1 + + + + + + deleteVolume + [52] deleteVolume1-TestCase + + + id + getdatavolumeid2 + + + + + + deleteVolume + [53] deleteVolume2-TestCase + + + id + getdatavolumeid3 + + + + + + + + destroyVirtualMachine + [54] destroyVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + recoverVirtualMachine + [55] recoverVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + startVirtualMachine + [56] startVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + rebootVirtualMachine + [57] rebootVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + stopVirtualMachine + [58] stopVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + destroyVirtualMachine + [59] destroyVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + recoverVirtualMachine + [60] recoverVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + changeServiceForVirtualMachine + [61] changeServiceForVirtualMachine-TestCase + + + id + globalisovmid1 + + + serviceOfferingId + globalchangeservoff + + + + + + startVirtualMachine + [62] startVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + rebootVirtualMachine + [63] rebootVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + stopVirtualMachine + [64] stopVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + destroyVirtualMachine + [65] destroyVirtualMachine-TestCase + + + id + globalisovmid1 + + + + + + deployVirtualMachine + [66] deployVirtualMachineFromTemplateCreatedFromSnapshotofROOTVolume-TestCase + + + zoneId + globalzoneid + + + hypervisor + globalhypervisortype + + + templateId + gettemplateid1 + + + serviceOfferingId + servofferid + + + networkIds + getnetworkid1 + + + diskOfferingId + diskofferid + + + account + accountname + + + domainid + globaldomainid + + + + + id + getvmid1 + + + + + + deleteTemplate + [67] deleteTemplateofSnapshotTakenofROOTVolume-TestCase + + + id + gettemplateid1 + + + zoneid + globalzoneid + + + + + + deleteTemplate + [68] deleteTemplateofSnapshotTakenofDATAVolume-TestCase + + + id + gettemplateid2 + + + zoneid + globalzoneid + + + + + + deleteTemplate + [69] deleteTemplateCreatedFromROOTVolume-TestCase + + + id + gettemplateid3 + + + zoneid + globalzoneid + + + + + + deleteSnapshot + [70] deleteSnapshot-TestCase + + + id + getsnapshotid1 + + + + + + deleteSnapshot + [71] deleteSnapshot-TestCase + + + id + getsnapshotid2 + + + + + + + + + + extractIso + [] extractIsoTestCase + + + id + getisoid1 + + + zoneid + 1 + + + mode + HTTP_DOWNLOAD + + + + + + deleteIso + [] deleteIsoTestCase + + + id + getisoid1 + + + zoneid + 1 + + + + + + deleteAccount + [72] deleteAccount-TestCase + + + id + getaccountid + + + + diff --git a/test/metadata/func/securitygroups.xml b/test/metadata/func/securitygroups.xml index 55fc32aa2702..57626b3b1a54 100644 --- a/test/metadata/func/securitygroups.xml +++ b/test/metadata/func/securitygroups.xml @@ -1,770 +1,770 @@ - - - - - - createUser - Create User and Account - - - username - - - password - apiuser - - - firstname - apiuser - - - lastname - apiuser - - - email - apiuser@gmail.com - - - accounttype - 0 - - - - - id - userid - - - account - accountname - - - - - - registerUserKeys - Registering the User - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetworkGroup - true - Create a Network Group - - - name - networkgrp-1 - - - - - - - - authorizeNetworkGroupIngress - Add the First Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.131.171/32 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - deployVirtualMachine - true - Deploy a VM with the Network Group Assigned - - - zoneid - globalzoneid - - - serviceofferingid - flatnetworkserviceofferingid - - - networkgrouplist - networkgrp-1 - - - templateid - globaltemplateid - - - - - id - vmid - - - ipaddress - vmip - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - Ssh access test for the VM - - - h - vmip - - - p - password - - - - - - sleep.sh - - Sleep for 1 min - - - s - 60 - - - - - - stopVirtualMachine - true - Stopping VM - - - id - vmid - - - - - - startVirtualMachine - true - Starting VM - - - id - vmid - - - - - - rebootVirtualMachine - true - Rebooting VM - - - id - vmid - - - - - - revokeNetworkGroupIngress - Revoke The First Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.131.171/32 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - ssh.sh - - true - Ssh access test for the vm - should fail as ingress rule is revoked - - - h - vmip - - - p - password - - - - - - - - authorizeNetworkGroupIngress - Add the Second Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.131.172/32 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - deployVirtualMachine - true - Deploy a VM with the Network Group Assigned - - - zoneid - globalzoneid - - - serviceofferingid - flatnetworkserviceofferingid - - - networkgrouplist - networkgrp-1 - - - templateid - globaltemplateid - - - - - id - vmid - - - ipaddress - vmip - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - true - Ssh test for the vm - should fail as no access provided by ingress rule - - - h - vmip - - - p - password - - - - - - revokeNetworkGroupIngress - Revoke The Second Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.131.172/32 - - - endport - 22 - - - startport - 22 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - - - authorizeNetworkGroupIngress - Add the Third Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.131.0/24 - - - endport - 80 - - - startport - 80 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - deployVirtualMachine - true - Deploy a VM with the Network Group Assigned - - - zoneid - globalzoneid - - - serviceofferingid - flatnetworkserviceofferingid - - - networkgrouplist - networkgrp-1 - - - templateid - globaltemplateid - - - - - id - vmid - - - ipaddress - vmip - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - true - Ssh test for the vm - should fail as no access provided by ingress rule - - - h - vmip - - - p - password - - - - - - revokeNetworkGroupIngress - Revoke The Third Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.131.0/24 - - - endport - 80 - - - startport - 80 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - - - authorizeNetworkGroupIngress - Add the Fourth Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.130.0/24 - - - endport - 80 - - - startport - 80 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - deployVirtualMachine - true - Deploy a VM with the Network Group Assigned - - - zoneid - globalzoneid - - - serviceofferingid - flatnetworkserviceofferingid - - - networkgrouplist - networkgrp-1 - - - templateid - globaltemplateid - - - - - id - vmid - - - ipaddress - vmip - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - ssh.sh - - true - Ssh test for the vm - should fail as no access provided by ingress rule - - - h - vmip - - - p - password - - - - - - revokeNetworkGroupIngress - Revoke The Fourth Ingress Rule - - - networkgroupname - networkgrp-1 - - - cidrlist - 192.168.130.0/24 - - - endport - 80 - - - startport - 80 - - - protocol - tcp - - - account - accountname - - - domainid - 1 - - - - - - - - deleteNetworkGroup - true - Delete First network group - - - name - networkgrp-1 - - - - - - destroyVirtualMachine - true - Destroying virtual machine - - - id - vmid - - - - - - sleep.sh - - Sleep for 2 min - - - s - 120 - - - - - - - - deleteUser - Deleting the user - - - id - userid - - - - - + + + + + + createUser + Create User and Account + + + username + + + password + apiuser + + + firstname + apiuser + + + lastname + apiuser + + + email + apiuser@gmail.com + + + accounttype + 0 + + + + + id + userid + + + account + accountname + + + + + + registerUserKeys + Registering the User + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetworkGroup + true + Create a Network Group + + + name + networkgrp-1 + + + + + + + + authorizeNetworkGroupIngress + Add the First Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.131.171/32 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + deployVirtualMachine + true + Deploy a VM with the Network Group Assigned + + + zoneid + globalzoneid + + + serviceofferingid + flatnetworkserviceofferingid + + + networkgrouplist + networkgrp-1 + + + templateid + globaltemplateid + + + + + id + vmid + + + ipaddress + vmip + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + Ssh access test for the VM + + + h + vmip + + + p + password + + + + + + sleep.sh + + Sleep for 1 min + + + s + 60 + + + + + + stopVirtualMachine + true + Stopping VM + + + id + vmid + + + + + + startVirtualMachine + true + Starting VM + + + id + vmid + + + + + + rebootVirtualMachine + true + Rebooting VM + + + id + vmid + + + + + + revokeNetworkGroupIngress + Revoke The First Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.131.171/32 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + ssh.sh + + true + Ssh access test for the vm - should fail as ingress rule is revoked + + + h + vmip + + + p + password + + + + + + + + authorizeNetworkGroupIngress + Add the Second Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.131.172/32 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + deployVirtualMachine + true + Deploy a VM with the Network Group Assigned + + + zoneid + globalzoneid + + + serviceofferingid + flatnetworkserviceofferingid + + + networkgrouplist + networkgrp-1 + + + templateid + globaltemplateid + + + + + id + vmid + + + ipaddress + vmip + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + true + Ssh test for the vm - should fail as no access provided by ingress rule + + + h + vmip + + + p + password + + + + + + revokeNetworkGroupIngress + Revoke The Second Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.131.172/32 + + + endport + 22 + + + startport + 22 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + + + authorizeNetworkGroupIngress + Add the Third Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.131.0/24 + + + endport + 80 + + + startport + 80 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + deployVirtualMachine + true + Deploy a VM with the Network Group Assigned + + + zoneid + globalzoneid + + + serviceofferingid + flatnetworkserviceofferingid + + + networkgrouplist + networkgrp-1 + + + templateid + globaltemplateid + + + + + id + vmid + + + ipaddress + vmip + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + true + Ssh test for the vm - should fail as no access provided by ingress rule + + + h + vmip + + + p + password + + + + + + revokeNetworkGroupIngress + Revoke The Third Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.131.0/24 + + + endport + 80 + + + startport + 80 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + + + authorizeNetworkGroupIngress + Add the Fourth Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.130.0/24 + + + endport + 80 + + + startport + 80 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + deployVirtualMachine + true + Deploy a VM with the Network Group Assigned + + + zoneid + globalzoneid + + + serviceofferingid + flatnetworkserviceofferingid + + + networkgrouplist + networkgrp-1 + + + templateid + globaltemplateid + + + + + id + vmid + + + ipaddress + vmip + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + ssh.sh + + true + Ssh test for the vm - should fail as no access provided by ingress rule + + + h + vmip + + + p + password + + + + + + revokeNetworkGroupIngress + Revoke The Fourth Ingress Rule + + + networkgroupname + networkgrp-1 + + + cidrlist + 192.168.130.0/24 + + + endport + 80 + + + startport + 80 + + + protocol + tcp + + + account + accountname + + + domainid + 1 + + + + + + + + deleteNetworkGroup + true + Delete First network group + + + name + networkgrp-1 + + + + + + destroyVirtualMachine + true + Destroying virtual machine + + + id + vmid + + + + + + sleep.sh + + Sleep for 2 min + + + s + 120 + + + + + + + + deleteUser + Deleting the user + + + id + userid + + + + + diff --git a/test/metadata/func/sharedstorage_volume_test.xml b/test/metadata/func/sharedstorage_volume_test.xml index 697887998ecd..8f55a64b30eb 100644 --- a/test/metadata/func/sharedstorage_volume_test.xml +++ b/test/metadata/func/sharedstorage_volume_test.xml @@ -1,1374 +1,1374 @@ - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - apiuser@gmail.com - - - firstname - apiuser - - - lastname - apiuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountvar - - - - - id - userid - - - - - - - - registerUserKeys - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar - - - domainid - 1 - - - name - sharedstoragevolumeregressionnetwork - - - displaytext - sharedstoragevolumeregressionnetwork - - - - - id - networkid - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-1 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volumeid - - - - - - attachVolume - true - 1028 - - - id - volumeid - - - virtualmachineid - vmid - - - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volumeid - - - - - - attachVolume - true - 1029 - - - id - volumeid - - - virtualmachineid - vmid - - - - - - - - createVolume - true - 1030 - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - type - DATADISK - - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - - - - detachVolume - true - 1031 - - - id - datavolumeid - - - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - detachVolume - true - 1033 - true - - - id - datavolumeid - - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-3 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volume1 - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volume2 - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volume3 - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volume4 - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volume5 - - - - - - attachVolume - true - - - id - volume1 - - - virtualmachineid - vmid - - - - - - attachVolume - true - - - id - volume2 - - - virtualmachineid - vmid - - - - - - attachVolume - true - - - id - volume3 - - - virtualmachineid - vmid - - - - - - attachVolume - true - - - id - volume4 - - - virtualmachineid - vmid - - - - - - attachVolume - true - 1035 - - - id - volume5 - - - virtualmachineid - vmid - - - - - - - - rebootVirtualMachine - true - 1036 - - - id - vmid - - - - - - - stopVirtualMachine - true - 1037 - - - id - vmid - - - - - - startVirtualMachine - true - 1037 - - - id - vmid - - - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volume6 - - - - - - attachVolume - true - true - 1039 - - - id - volume6 - - - virtualmachineid - vmid - - - - - - - - destroyVirtualMachine - true - 1038 - - - id - vmid - - - - - - listVolumes - true - 1038 - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-4 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - - - - detachVolume - true - - - id - datavolumeid - - - - - - rebootVirtualMachine - true - 1040 - - - id - vmid - - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - startVirtualMachine - true - 1041 - - - id - vmid - - - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-5 - - - group - group1 - - - networkids - networkid - - - - - id - vmid - - - - - - listVolumes - - - virtualmachineid - vmid - - - type - DATADISK - - - - - id - datavolumeid - - - - - - detachVolume - true - 1044 - - - id - datavolumeid - - - - - - listVolumes - true - 1044 - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - attachVolume - true - - - id - datavolumeid - - - virtualmachineid - vmid - - - - - - stopVirtualMachine - true - - - id - vmid - - - - - - detachVolume - true - 1045 - - - id - datavolumeid - - - - - - listVolumes - true - 1045 - - - virtualmachineid - vmid - - - type - DATADISK - - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-6 - - - group - group1 - - - networkids - networkid - - - - - id - vmid1 - - - - - - deployVirtualMachine - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SharedStorage-VM-7 - - - group - group1 - - - networkids - networkid - - - - - id - vmid2 - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volumeid - - - - - - attachVolume - true - - - id - volumeid - - - virtualmachineid - vmid1 - - - - - - attachVolume - true - true - 1046 - - - id - volumeid - - - virtualmachineid - vmid2 - - - - - - - destroyVirtualMachine - true - - - id - vmid1 - - - - - - createVolume - true - - - name - - - zoneid - globalzoneid - - - diskofferingid - globaldiskofferingid - - - - - id - volumeid - - - - - - attachVolume - true - 1081 - true - - - id - volumeid - - - virtualmachineid - vmid1 - - - - - - - listVolumes - - - virtualmachineid - vmid2 - - - type - DATADISK - - - - - id - datavolumeid - - - - - - destroyVirtualMachine - true - - - id - vmid2 - - - - - - detachVolume - true - 1082 - true - - - id - datavolumeid - - - - - - - - deleteAccount - Deleting Account as a part of cleanup - - - id - accountid - - - - - - + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + apiuser@gmail.com + + + firstname + apiuser + + + lastname + apiuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountvar + + + + + id + userid + + + + + + + + registerUserKeys + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar + + + domainid + 1 + + + name + sharedstoragevolumeregressionnetwork + + + displaytext + sharedstoragevolumeregressionnetwork + + + + + id + networkid + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-1 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volumeid + + + + + + attachVolume + true + 1028 + + + id + volumeid + + + virtualmachineid + vmid + + + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volumeid + + + + + + attachVolume + true + 1029 + + + id + volumeid + + + virtualmachineid + vmid + + + + + + + + createVolume + true + 1030 + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + type + DATADISK + + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + + + + detachVolume + true + 1031 + + + id + datavolumeid + + + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + detachVolume + true + 1033 + true + + + id + datavolumeid + + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-3 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volume1 + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volume2 + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volume3 + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volume4 + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volume5 + + + + + + attachVolume + true + + + id + volume1 + + + virtualmachineid + vmid + + + + + + attachVolume + true + + + id + volume2 + + + virtualmachineid + vmid + + + + + + attachVolume + true + + + id + volume3 + + + virtualmachineid + vmid + + + + + + attachVolume + true + + + id + volume4 + + + virtualmachineid + vmid + + + + + + attachVolume + true + 1035 + + + id + volume5 + + + virtualmachineid + vmid + + + + + + + + rebootVirtualMachine + true + 1036 + + + id + vmid + + + + + + + stopVirtualMachine + true + 1037 + + + id + vmid + + + + + + startVirtualMachine + true + 1037 + + + id + vmid + + + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volume6 + + + + + + attachVolume + true + true + 1039 + + + id + volume6 + + + virtualmachineid + vmid + + + + + + + + destroyVirtualMachine + true + 1038 + + + id + vmid + + + + + + listVolumes + true + 1038 + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-4 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + + + + detachVolume + true + + + id + datavolumeid + + + + + + rebootVirtualMachine + true + 1040 + + + id + vmid + + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + startVirtualMachine + true + 1041 + + + id + vmid + + + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-5 + + + group + group1 + + + networkids + networkid + + + + + id + vmid + + + + + + listVolumes + + + virtualmachineid + vmid + + + type + DATADISK + + + + + id + datavolumeid + + + + + + detachVolume + true + 1044 + + + id + datavolumeid + + + + + + listVolumes + true + 1044 + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + attachVolume + true + + + id + datavolumeid + + + virtualmachineid + vmid + + + + + + stopVirtualMachine + true + + + id + vmid + + + + + + detachVolume + true + 1045 + + + id + datavolumeid + + + + + + listVolumes + true + 1045 + + + virtualmachineid + vmid + + + type + DATADISK + + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-6 + + + group + group1 + + + networkids + networkid + + + + + id + vmid1 + + + + + + deployVirtualMachine + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SharedStorage-VM-7 + + + group + group1 + + + networkids + networkid + + + + + id + vmid2 + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volumeid + + + + + + attachVolume + true + + + id + volumeid + + + virtualmachineid + vmid1 + + + + + + attachVolume + true + true + 1046 + + + id + volumeid + + + virtualmachineid + vmid2 + + + + + + + destroyVirtualMachine + true + + + id + vmid1 + + + + + + createVolume + true + + + name + + + zoneid + globalzoneid + + + diskofferingid + globaldiskofferingid + + + + + id + volumeid + + + + + + attachVolume + true + 1081 + true + + + id + volumeid + + + virtualmachineid + vmid1 + + + + + + + listVolumes + + + virtualmachineid + vmid2 + + + type + DATADISK + + + + + id + datavolumeid + + + + + + destroyVirtualMachine + true + + + id + vmid2 + + + + + + detachVolume + true + 1082 + true + + + id + datavolumeid + + + + + + + + deleteAccount + Deleting Account as a part of cleanup + + + id + accountid + + + + + + diff --git a/test/metadata/func/snapshot_iso.xml b/test/metadata/func/snapshot_iso.xml index 6bddb393e71c..4075ce7a6eb8 100644 --- a/test/metadata/func/snapshot_iso.xml +++ b/test/metadata/func/snapshot_iso.xml @@ -1,646 +1,646 @@ - - - - - - - - createUser - Create a user - - - - username - - - password - apiuser - - - firstname - apiuser - - - lastname - apiuser - - - email - apiuser@gmail.com - - - accounttype - 0 - - - - - id - userid - - - account - accountvar - - - - - - - registerUserKeys - Registering the user - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - - registerIso - Register iso - - - name - isoname - - - displaytext - isodisplay - - - url - http://nfs1.lab.vmops.com/isos_64bit/Ubuntu-10.04/ubuntu-10.04-desktop-amd64.iso - - - ostypeid - 59 - - - zoneid - globalzoneid - - - account - accountname - - - domainid - 1 - - - - - id - privateisoid - - - created - - - ispublic - false - - - bootable - true - - - isready - false - - - isfeatured - false - - - name - registername - - - displaytext - registerdisplay - - - - - - - deployVirtualMachine - Deploying VM from small diskOffering - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - 5 - - - templateid - privateisoid - - - account - admin - - - domainid - 1 - - - displayname - namefordisplay - - - group - group1 - - - - - id - smallvmid - - - - - - - listVolumes - - - virtualmachineid - smallvmid - - - type - ROOT - - - - - id - smallrootvolumeid - - - - - - listVolumes - - - virtualmachineid - smallvmid - - - type - datadisk - - - - - id - smalldatavolumeid - - - - - - - sleep.sh - - Sleep for some time - - - s - 200 - - - - - - - createSnapshot - Creating snapshot of ROOT disk of small vm - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - - createSnapshot - Creating snapshot of Data disk of small vm - true - - - volumeid - smalldatavolumeid - - - - - id - smalldatasnapshot - - - - - - - createTemplate - Creating template of ROOT snapshot of small vm - true - - - snapshotid - smallrootsnapshot - - - name - - - displaytext - newtext - - - osTypeId - 59 - - - - - id - smalltemplateid - - - - - - - deployVirtualMachine - Deploy a vm from template created from snapshot of ROOT disk of small vm - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - 5 - - - templateid - smalltemplateid - - - account - admin - - - domainid - 1 - - - displayname - smallsnpshtVM - - - group - group1 - - - - - id - smallvmid1 - - - - - - - createVolume - Create a volume from smaller disk snapshot - true - - - snapshotid - smallrootsnapshot - - - name - - - - - id - smallvolumeid - - - - - - - createVolume - Create a volume from smaller disk snapshot - true - - - snapshotid - smalldatasnapshot - - - name - - - - - id - smallvolumeid - - - - - - - attachVolume - Attach small volume to the vm created from small snapshot - true - - - id - smallvolumeid - - - virtualmachineid - smallvmid1 - - - - - - - - attachVolume - Attach small volume to a different VM - true - - - id - smallvolumeid - - - virtualmachineid - smallvmid - - - - - - listConfiguration - Getting value of the expunge.delay configuration parameter - - - name - expunge.delay - - - - - value - expunge.delay - - - - - - - - rebootVirtualMachine - Reboot small vm - - - id - smallvmid1 - - - - - - stopVirtualMachine - stop medium vm - - - id - smallvmid1 - - - - - - startVirtualMachine - stop medium vm - - - id - smallvmid1 - - - - - - - deleteSnapshot - Deleting a snapshot taken of an attached Volume - - - id - smallrootsnapshot - - - - - - - destroyVirtualMachine - Destroy small vm - - - id - smallvmid1 - - - - - - - - - - createSnapshot - Creating snapshot of root disk immediately after destroying VM. - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - - sleep.sh - - Sleep for expunge delayed interval - - - s - expunge.delay - - - - - - sleep.sh - - Sleep for another expunge delayed interval - - - s - expunge.delay - - - - - - - - - createSnapshot - Creating snapshot of Destroyed root disk after expunge delay Failure Test Case - true - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - - destroyVirtualMachine - Destroy small vm - - - id - smallvmid1 - - - - - - - - deleteUser - Deleting a user - - - id - userid - - - - - + + + + + + + + createUser + Create a user + + + + username + + + password + apiuser + + + firstname + apiuser + + + lastname + apiuser + + + email + apiuser@gmail.com + + + accounttype + 0 + + + + + id + userid + + + account + accountvar + + + + + + + registerUserKeys + Registering the user + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + + registerIso + Register iso + + + name + isoname + + + displaytext + isodisplay + + + url + http://nfs1.lab.vmops.com/isos_64bit/Ubuntu-10.04/ubuntu-10.04-desktop-amd64.iso + + + ostypeid + 59 + + + zoneid + globalzoneid + + + account + accountname + + + domainid + 1 + + + + + id + privateisoid + + + created + + + ispublic + false + + + bootable + true + + + isready + false + + + isfeatured + false + + + name + registername + + + displaytext + registerdisplay + + + + + + + deployVirtualMachine + Deploying VM from small diskOffering + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + 5 + + + templateid + privateisoid + + + account + admin + + + domainid + 1 + + + displayname + namefordisplay + + + group + group1 + + + + + id + smallvmid + + + + + + + listVolumes + + + virtualmachineid + smallvmid + + + type + ROOT + + + + + id + smallrootvolumeid + + + + + + listVolumes + + + virtualmachineid + smallvmid + + + type + datadisk + + + + + id + smalldatavolumeid + + + + + + + sleep.sh + + Sleep for some time + + + s + 200 + + + + + + + createSnapshot + Creating snapshot of ROOT disk of small vm + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + + createSnapshot + Creating snapshot of Data disk of small vm + true + + + volumeid + smalldatavolumeid + + + + + id + smalldatasnapshot + + + + + + + createTemplate + Creating template of ROOT snapshot of small vm + true + + + snapshotid + smallrootsnapshot + + + name + + + displaytext + newtext + + + osTypeId + 59 + + + + + id + smalltemplateid + + + + + + + deployVirtualMachine + Deploy a vm from template created from snapshot of ROOT disk of small vm + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + 5 + + + templateid + smalltemplateid + + + account + admin + + + domainid + 1 + + + displayname + smallsnpshtVM + + + group + group1 + + + + + id + smallvmid1 + + + + + + + createVolume + Create a volume from smaller disk snapshot + true + + + snapshotid + smallrootsnapshot + + + name + + + + + id + smallvolumeid + + + + + + + createVolume + Create a volume from smaller disk snapshot + true + + + snapshotid + smalldatasnapshot + + + name + + + + + id + smallvolumeid + + + + + + + attachVolume + Attach small volume to the vm created from small snapshot + true + + + id + smallvolumeid + + + virtualmachineid + smallvmid1 + + + + + + + + attachVolume + Attach small volume to a different VM + true + + + id + smallvolumeid + + + virtualmachineid + smallvmid + + + + + + listConfiguration + Getting value of the expunge.delay configuration parameter + + + name + expunge.delay + + + + + value + expunge.delay + + + + + + + + rebootVirtualMachine + Reboot small vm + + + id + smallvmid1 + + + + + + stopVirtualMachine + stop medium vm + + + id + smallvmid1 + + + + + + startVirtualMachine + stop medium vm + + + id + smallvmid1 + + + + + + + deleteSnapshot + Deleting a snapshot taken of an attached Volume + + + id + smallrootsnapshot + + + + + + + destroyVirtualMachine + Destroy small vm + + + id + smallvmid1 + + + + + + + + + + createSnapshot + Creating snapshot of root disk immediately after destroying VM. + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + + sleep.sh + + Sleep for expunge delayed interval + + + s + expunge.delay + + + + + + sleep.sh + + Sleep for another expunge delayed interval + + + s + expunge.delay + + + + + + + + + createSnapshot + Creating snapshot of Destroyed root disk after expunge delay Failure Test Case + true + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + + destroyVirtualMachine + Destroy small vm + + + id + smallvmid1 + + + + + + + + deleteUser + Deleting a user + + + id + userid + + + + + diff --git a/test/metadata/func/snapshots.xml b/test/metadata/func/snapshots.xml index 843b5dfe1c59..b1f49d404133 100644 --- a/test/metadata/func/snapshots.xml +++ b/test/metadata/func/snapshots.xml @@ -1,827 +1,827 @@ - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountvar - - - accounttype - 0 - - - domainid - 1 - - - email - apiuser@gmail.com - - - firstname - apiuser - - - lastname - apiuserlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountvar - - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountvar - - - - - id - userid - - - - - - - registerUserKeys - Registering the user - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountvar - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - deployVirtualMachine - Deploying vm from small diskOffering - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - displayname - SnapshotsTest-VM-1 - - - group - group1 - - - networkids - networkid - - - - - id - smallvmid - - - - - - deployVirtualMachine - Deploying vm from Medium disk offering - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - mediumglobaldiskofferingid - - - templateid - globaltemplateid - - - account - accountvar - - - domainid - 1 - - - account - accountvar - - - domainid - 1 - - - displayname - SnapshotsTest-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - largevmid - - - - - - listVolumes - - - virtualmachineid - smallvmid - - - type - ROOT - - - - - id - smallrootvolumeid - - - - - - listVolumes - - - virtualmachineid - smallvmid - - - type - datadisk - - - - - id - smalldatavolumeid - - - - - - listVolumes - - - virtualmachineid - largevmid - - - type - ROOT - - - - - id - largerootvolumeid - - - - - - listVolumes - - - virtualmachineid - largevmid - - - type - datadisk - - - - - id - largedatavolumeid - - - - - - - sleep.sh - - Sleep for some time - - - s - 200 - - - - - - - createSnapshot - Creating snapshot of ROOT disk of small vm - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - createSnapshot - Creating snapshot of Data disk of small vm - true - - - volumeid - smalldatavolumeid - - - - - id - smalldatasnapshot - - - - - - createSnapshot - Creating snapshot of ROOT disk of medium vm - true - - - volumeid - largerootvolumeid - - - - - id - largerootsnapshot - - - - - - createSnapshot - Creating snapshot of DATA disk of medium vm - true - - - volumeid - largedatavolumeid - - - - - id - largedatasnapshot - - - - - - - createTemplate - Creating template of ROOT snapshot of small vm - true - - - snapshotid - smallrootsnapshot - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - smalltemplateid - - - - - - createTemplate - Creating template of ROOT snapshot of large vm - true - - - snapshotid - largerootsnapshot - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - largetemplateid - - - - - - deployVirtualMachine - Deploy a vm from template created from snapshot of ROOT disk of small vm - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - smalltemplateid - - - account - accountvar - - - domainid - 1 - - - account - accountvar - - - domainid - 1 - - - displayname - SnapshotsTest-VM-3 - - - group - group1 - - - networkids - networkid - - - - - id - smallvmid1 - - - - - - deployVirtualMachine - Deploy a vm from template created from snapshot of ROOT disk of medium vm - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - mediumglobaldiskofferinigid - - - templateid - largetemplateid - - - account - accountvar - - - domainid - 1 - - - account - accountvar - - - domainid - 1 - - - displayname - SnapshotsTest-VM-4 - - - group - group1 - - - networkids - networkid - - - - - id - largevmid1 - - - - - - - - createVolume - Create a volume from smaller disk snapshot - true - - - snapshotid - smallrootsnapshot - - - name - - - - - id - smallvolumeid - - - - - - createVolume - Create a volume from larger disk snapshot - true - - - snapshotid - largerootsnapshot - - - name - - - - - id - largevolumeid - - - - - - attachVolume - Attach small volume to the vm created from small snapshot - true - - - id - smallvolumeid - - - virtualmachineid - smallvmid1 - - - - - - attachVolume - Attach medium volume to the vm created from medium snapshot - true - - - id - largevolumeid - - - virtualmachineid - largevmid1 - - - - - - - - rebootVirtualMachine - Reboot small vm - - - id - smallvmid1 - - - - - - stopVirtualMachine - stop medium vm - - - id - largevmid1 - - - - - - startVirtualMachine - stop medium vm - - - id - largevmid1 - - - - - - - - destroyVirtualMachine - Destroy small vm - - - id - smallvmid1 - - - - - - destroyVirtualMachine - Destroy large vm - - - id - largevmid1 - - - - - - - - - deleteAccount - Deleting a user - - - id - accountid - - - - - - - - + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountvar + + + accounttype + 0 + + + domainid + 1 + + + email + apiuser@gmail.com + + + firstname + apiuser + + + lastname + apiuserlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountvar + + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountvar + + + + + id + userid + + + + + + + registerUserKeys + Registering the user + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountvar + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + deployVirtualMachine + Deploying vm from small diskOffering + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + displayname + SnapshotsTest-VM-1 + + + group + group1 + + + networkids + networkid + + + + + id + smallvmid + + + + + + deployVirtualMachine + Deploying vm from Medium disk offering + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + mediumglobaldiskofferingid + + + templateid + globaltemplateid + + + account + accountvar + + + domainid + 1 + + + account + accountvar + + + domainid + 1 + + + displayname + SnapshotsTest-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + largevmid + + + + + + listVolumes + + + virtualmachineid + smallvmid + + + type + ROOT + + + + + id + smallrootvolumeid + + + + + + listVolumes + + + virtualmachineid + smallvmid + + + type + datadisk + + + + + id + smalldatavolumeid + + + + + + listVolumes + + + virtualmachineid + largevmid + + + type + ROOT + + + + + id + largerootvolumeid + + + + + + listVolumes + + + virtualmachineid + largevmid + + + type + datadisk + + + + + id + largedatavolumeid + + + + + + + sleep.sh + + Sleep for some time + + + s + 200 + + + + + + + createSnapshot + Creating snapshot of ROOT disk of small vm + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + createSnapshot + Creating snapshot of Data disk of small vm + true + + + volumeid + smalldatavolumeid + + + + + id + smalldatasnapshot + + + + + + createSnapshot + Creating snapshot of ROOT disk of medium vm + true + + + volumeid + largerootvolumeid + + + + + id + largerootsnapshot + + + + + + createSnapshot + Creating snapshot of DATA disk of medium vm + true + + + volumeid + largedatavolumeid + + + + + id + largedatasnapshot + + + + + + + createTemplate + Creating template of ROOT snapshot of small vm + true + + + snapshotid + smallrootsnapshot + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + smalltemplateid + + + + + + createTemplate + Creating template of ROOT snapshot of large vm + true + + + snapshotid + largerootsnapshot + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + largetemplateid + + + + + + deployVirtualMachine + Deploy a vm from template created from snapshot of ROOT disk of small vm + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + smalltemplateid + + + account + accountvar + + + domainid + 1 + + + account + accountvar + + + domainid + 1 + + + displayname + SnapshotsTest-VM-3 + + + group + group1 + + + networkids + networkid + + + + + id + smallvmid1 + + + + + + deployVirtualMachine + Deploy a vm from template created from snapshot of ROOT disk of medium vm + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + mediumglobaldiskofferinigid + + + templateid + largetemplateid + + + account + accountvar + + + domainid + 1 + + + account + accountvar + + + domainid + 1 + + + displayname + SnapshotsTest-VM-4 + + + group + group1 + + + networkids + networkid + + + + + id + largevmid1 + + + + + + + + createVolume + Create a volume from smaller disk snapshot + true + + + snapshotid + smallrootsnapshot + + + name + + + + + id + smallvolumeid + + + + + + createVolume + Create a volume from larger disk snapshot + true + + + snapshotid + largerootsnapshot + + + name + + + + + id + largevolumeid + + + + + + attachVolume + Attach small volume to the vm created from small snapshot + true + + + id + smallvolumeid + + + virtualmachineid + smallvmid1 + + + + + + attachVolume + Attach medium volume to the vm created from medium snapshot + true + + + id + largevolumeid + + + virtualmachineid + largevmid1 + + + + + + + + rebootVirtualMachine + Reboot small vm + + + id + smallvmid1 + + + + + + stopVirtualMachine + stop medium vm + + + id + largevmid1 + + + + + + startVirtualMachine + stop medium vm + + + id + largevmid1 + + + + + + + + destroyVirtualMachine + Destroy small vm + + + id + smallvmid1 + + + + + + destroyVirtualMachine + Destroy large vm + + + id + largevmid1 + + + + + + + + + deleteAccount + Deleting a user + + + id + accountid + + + + + + + + diff --git a/test/metadata/func/snapshots_contd.xml b/test/metadata/func/snapshots_contd.xml index c9b277cb6969..93b6cc40567a 100644 --- a/test/metadata/func/snapshots_contd.xml +++ b/test/metadata/func/snapshots_contd.xml @@ -1,626 +1,626 @@ - - - - - - - - createUser - Create a user - - - - username - - - password - apiuser - - - firstname - apiuser - - - lastname - apiuser - - - email - apiuser@gmail.com - - - accounttype - 0 - - - - - id - userid - - - account - accountvar - - - - - - - registerUserKeys - Registering the user - - - id - userid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - - deployVirtualMachine - Deploying VM from small diskOffering - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - 5 - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - namefordisplay - - - group - group1 - - - - - id - smallvmid - - - - - - - listVolumes - - - virtualmachineid - smallvmid - - - type - ROOT - - - - - id - smallrootvolumeid - - - - - - listVolumes - - - virtualmachineid - smallvmid - - - type - datadisk - - - - - id - smalldatavolumeid - - - - - - - sleep.sh - - Sleep for some time - - - s - 200 - - - - - - - createSnapshot - Creating snapshot of ROOT disk of small vm - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - - createSnapshot - Creating snapshot of Data disk of small vm - true - - - volumeid - smalldatavolumeid - - - - - id - smalldatasnapshot - - - - - - - createTemplate - Creating template of ROOT snapshot of small vm - true - - - snapshotid - smallrootsnapshot - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - smalltemplateid - - - - - - - deployVirtualMachine - Deploy a vm from template created from snapshot of ROOT disk of small vm - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - 5 - - - templateid - smalltemplateid - - - account - admin - - - domainid - 1 - - - displayname - smallsnpshtVM - - - group - group1 - - - - - id - smallvmid1 - - - - - - - createVolume - Create a volume from smaller disk snapshot - true - - - snapshotid - smallrootsnapshot - - - name - - - - - id - smallvolumeid - - - - - - - createVolume - Create a volume from smaller disk snapshot - true - - - snapshotid - smalldatasnapshot - - - name - - - - - id - smallvolumeid - - - - - - - createSnapshot - Creating snapshot of Detached Volume - true - - - volumeid - smallvolumeid - - - - - id - detachsnapshotid - - - - - - - attachVolume - Attach small volume to the vm created from small snapshot - true - - - id - smallvolumeid - - - virtualmachineid - smallvmid1 - - - - - - - - attachVolume - Attach small volume to a different VM - true - - - id - smallvolumeid - - - virtualmachineid - smallvmid - - - - - - - - - rebootVirtualMachine - Reboot small vm - - - id - smallvmid1 - - - - - - stopVirtualMachine - stop medium vm - - - id - smallvmid1 - - - - - - startVirtualMachine - stop medium vm - - - id - smallvmid1 - - - - - - - deleteSnapshot - Deleting a snapshot taken of an attached Volume - - - id - smallrootsnapshot - - - - - - - deleteSnapshot - Deleting a snapshot taken of a detached Volume - - - id - detachsnapshotid - - - - - - - - destroyVirtualMachine - Destroy small vm - - - id - smallvmid1 - - - - - - - - - - createSnapshot - Creating snapshot of root disk immediately after destroying VM. - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - - sleep.sh - - Sleep for expunge delayed interval - - - s - expunge.delay - - - - - - sleep.sh - - Sleep for another expunge delayed interval - - - s - expunge.delay - - - - - - - - - createSnapshot - Creating snapshot of Destroyed root disk after expunge delay Failure Test Case - true - true - - - volumeid - smallrootvolumeid - - - - - id - smallrootsnapshot - - - - - - - destroyVirtualMachine - Destroy small vm - - - id - smallvmid1 - - - - - - listConfiguration - Getting value of the expunge.delay configuration parameter - - - name - expunge.delay - - - - - value - expunge.delay - - - - - - listConfiguration - Getting value of the expunge.delay configuration parameter - - - name - expunge.delay - - - - - value - expunge.delay - - - - - - - deleteUser - Deleting a user - - - id - userid - - - - - + + + + + + + + createUser + Create a user + + + + username + + + password + apiuser + + + firstname + apiuser + + + lastname + apiuser + + + email + apiuser@gmail.com + + + accounttype + 0 + + + + + id + userid + + + account + accountvar + + + + + + + registerUserKeys + Registering the user + + + id + userid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + + deployVirtualMachine + Deploying VM from small diskOffering + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + 5 + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + namefordisplay + + + group + group1 + + + + + id + smallvmid + + + + + + + listVolumes + + + virtualmachineid + smallvmid + + + type + ROOT + + + + + id + smallrootvolumeid + + + + + + listVolumes + + + virtualmachineid + smallvmid + + + type + datadisk + + + + + id + smalldatavolumeid + + + + + + + sleep.sh + + Sleep for some time + + + s + 200 + + + + + + + createSnapshot + Creating snapshot of ROOT disk of small vm + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + + createSnapshot + Creating snapshot of Data disk of small vm + true + + + volumeid + smalldatavolumeid + + + + + id + smalldatasnapshot + + + + + + + createTemplate + Creating template of ROOT snapshot of small vm + true + + + snapshotid + smallrootsnapshot + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + smalltemplateid + + + + + + + deployVirtualMachine + Deploy a vm from template created from snapshot of ROOT disk of small vm + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + 5 + + + templateid + smalltemplateid + + + account + admin + + + domainid + 1 + + + displayname + smallsnpshtVM + + + group + group1 + + + + + id + smallvmid1 + + + + + + + createVolume + Create a volume from smaller disk snapshot + true + + + snapshotid + smallrootsnapshot + + + name + + + + + id + smallvolumeid + + + + + + + createVolume + Create a volume from smaller disk snapshot + true + + + snapshotid + smalldatasnapshot + + + name + + + + + id + smallvolumeid + + + + + + + createSnapshot + Creating snapshot of Detached Volume + true + + + volumeid + smallvolumeid + + + + + id + detachsnapshotid + + + + + + + attachVolume + Attach small volume to the vm created from small snapshot + true + + + id + smallvolumeid + + + virtualmachineid + smallvmid1 + + + + + + + + attachVolume + Attach small volume to a different VM + true + + + id + smallvolumeid + + + virtualmachineid + smallvmid + + + + + + + + + rebootVirtualMachine + Reboot small vm + + + id + smallvmid1 + + + + + + stopVirtualMachine + stop medium vm + + + id + smallvmid1 + + + + + + startVirtualMachine + stop medium vm + + + id + smallvmid1 + + + + + + + deleteSnapshot + Deleting a snapshot taken of an attached Volume + + + id + smallrootsnapshot + + + + + + + deleteSnapshot + Deleting a snapshot taken of a detached Volume + + + id + detachsnapshotid + + + + + + + + destroyVirtualMachine + Destroy small vm + + + id + smallvmid1 + + + + + + + + + + createSnapshot + Creating snapshot of root disk immediately after destroying VM. + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + + sleep.sh + + Sleep for expunge delayed interval + + + s + expunge.delay + + + + + + sleep.sh + + Sleep for another expunge delayed interval + + + s + expunge.delay + + + + + + + + + createSnapshot + Creating snapshot of Destroyed root disk after expunge delay Failure Test Case + true + true + + + volumeid + smallrootvolumeid + + + + + id + smallrootsnapshot + + + + + + + destroyVirtualMachine + Destroy small vm + + + id + smallvmid1 + + + + + + listConfiguration + Getting value of the expunge.delay configuration parameter + + + name + expunge.delay + + + + + value + expunge.delay + + + + + + listConfiguration + Getting value of the expunge.delay configuration parameter + + + name + expunge.delay + + + + + value + expunge.delay + + + + + + + deleteUser + Deleting a user + + + id + userid + + + + + diff --git a/test/metadata/func/templatedwnldstress.xml b/test/metadata/func/templatedwnldstress.xml index 0edb82eb1fe2..38d7a13f2bbf 100644 --- a/test/metadata/func/templatedwnldstress.xml +++ b/test/metadata/func/templatedwnldstress.xml @@ -1,757 +1,757 @@ - - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - email - admin@gmail.com - - - firstname - admin - - - lastname - adminlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - - - - - createNetwork - [Creating default network] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - defaultregressionnetwork - - - displaytext - defaultregressionnetwork - - - - - id - networkid - - - - - - deployVirtualMachine - [Deploy a Virtual Machine under the Account] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - networkids - networkid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - - - - listVolumes - [List the ROOT Volume of the VM] - - - virtualmachineid - vmid - - - type - ROOT - - - - - id - rootvolumeid - - - - - - stopVirtualMachine - [Stop virtual machine] - - - id - vmid - - - - - - createTemplate - [Create template from root volume] - - - volumeid - rootvolumeid - - - name - - - displaytext - newtext - - - osTypeId - 12 - - - - - id - privatetemplateid - - - - - - deployVirtualMachine - [Deploying virtual machine from the Private Template] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - privatetemplateid - - - account - accountname - - - domainid - 1 - - - displayname - VMcreatedfrmTemplate - - - group - group1 - - - networkids - networkid - - - - - id - vmid1 - - - hostid - vm_hostid - - - - - - listHosts - List host where vm is running - - - id - vm_hostid - - - - - ipaddress - vmhost_ipaddress - - - - - - select install_path from template_spool_ref - true - Getting private template vdi from the db - - - template_id - privatetemplateid - - - - - install_path - template_uuid - - - - - - listvdi.sh - - List VDI for template on the xen host before vm is destroyed - - - h - vmhost_ipaddress - - - u - template_uuid - - - - - - destroyVirtualMachine - Destroy virtual machine created from the private template - - - id - vmid1 - - - - - - - listConfigurations - Getting expunge interval value - - - name - expunge.interval - - - - - value - expunge.interval - - - - - - updateConfiguration - Updating storage.cleanup.interval - - - name - storage.cleanup.interval - - - value - 60 - - - - - - listConfigurations - Getting expunge interval value - - - name - storage.cleanup.interval - - - - - value - storage.cleanup.interval - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - sleep.sh - - Sleep for expunge interval - - - s - expunge.interval - - - - - - - sleep.sh - - Sleep for another expunge interval - - - s - expunge.interval - - - - - - sleep.sh - - Sleep for Storage CleanUp interval - - - s - storage.cleanup.interval - - - - - - sleep.sh - - Sleep for another Storage CleanUp interval - - - s - storage.cleanup.interval - - - - - - - - listvdi.sh - true - - List VDI for template on the xen host after vm using template is destroyed - - - h - vmhost_ipaddress - - - u - template_uuid - - - - - - - - deployVirtualMachine - Deploying virtual machine from the private template - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - privatetemplateid - - - account - accountname - - - domainid - 1 - - - displayname - VMcreatedfrmTemplate - - - group - group1 - - - networkids - networkid - - - - - id - vmid1 - - - hostid - vm_hostid - - - - - - listHosts - List host where vm is running - - - id - vm_hostid - - - - - ipaddress - vmhost_ipaddress - - - - - - select install_path from template_spool_ref - true - Getting private template vdi from the db - - - template_id - privatetemplateid - - - - - install_path - template_uuid - - - - - - listvdi.sh - - List VDI for template on the xen host before vm is destroyed - - - h - vmhost_ipaddress - - - u - template_uuid - - - - - - destroyVirtualMachine - Destroy virtual machine created from the private template - - - id - vmid1 - - - - - - - listConfigurations - Getting expunge interval value - - - name - expunge.interval - - - - - value - expunge.interval - - - - - - sleep.sh - - Sleep for expunge interval - - - s - expunge.interval - - - - - - - sleep.sh - - Sleep for another expunge interval - - - s - expunge.interval - - - - - - sleep.sh - - Sleep for Storage CleanUp interval - - - s - storage.cleanup.interval - - - - - - sleep.sh - - Sleep for another Storage CleanUp interval - - - s - storage.cleanup.interval - - - - - - - - listvdi.sh - true - - List VDI for template on the xen host after vm using template is destroyed - - - h - vmhost_ipaddress - - - u - template_uuid - - - - - - - - - startVirtualMachine - [Start virtual machine] - - - id - vmid - - - - - state - Running - - - - - - rebootVirtualMachine - [Reboot Virtual Machine] - - - id - vmid - - - - - - destroyVirtualMachine - [Destroy virtual machine] - - - id - vmid - - - - - - listVirtualMachines - [Verify that vm was destroyed] - - - id - vmid - - - - - state - Destroyed - - - - - - deleteTemplate - Delete private template - - - id - privatetemplateid - - - - - - deleteAccount - [Delete account] - - - id - accountid - - - - - - + + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + email + admin@gmail.com + + + firstname + admin + + + lastname + adminlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + + + + + createNetwork + [Creating default network] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + defaultregressionnetwork + + + displaytext + defaultregressionnetwork + + + + + id + networkid + + + + + + deployVirtualMachine + [Deploy a Virtual Machine under the Account] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + networkids + networkid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + + + + listVolumes + [List the ROOT Volume of the VM] + + + virtualmachineid + vmid + + + type + ROOT + + + + + id + rootvolumeid + + + + + + stopVirtualMachine + [Stop virtual machine] + + + id + vmid + + + + + + createTemplate + [Create template from root volume] + + + volumeid + rootvolumeid + + + name + + + displaytext + newtext + + + osTypeId + 12 + + + + + id + privatetemplateid + + + + + + deployVirtualMachine + [Deploying virtual machine from the Private Template] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + privatetemplateid + + + account + accountname + + + domainid + 1 + + + displayname + VMcreatedfrmTemplate + + + group + group1 + + + networkids + networkid + + + + + id + vmid1 + + + hostid + vm_hostid + + + + + + listHosts + List host where vm is running + + + id + vm_hostid + + + + + ipaddress + vmhost_ipaddress + + + + + + select install_path from template_spool_ref + true + Getting private template vdi from the db + + + template_id + privatetemplateid + + + + + install_path + template_uuid + + + + + + listvdi.sh + + List VDI for template on the xen host before vm is destroyed + + + h + vmhost_ipaddress + + + u + template_uuid + + + + + + destroyVirtualMachine + Destroy virtual machine created from the private template + + + id + vmid1 + + + + + + + listConfigurations + Getting expunge interval value + + + name + expunge.interval + + + + + value + expunge.interval + + + + + + updateConfiguration + Updating storage.cleanup.interval + + + name + storage.cleanup.interval + + + value + 60 + + + + + + listConfigurations + Getting expunge interval value + + + name + storage.cleanup.interval + + + + + value + storage.cleanup.interval + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + sleep.sh + + Sleep for expunge interval + + + s + expunge.interval + + + + + + + sleep.sh + + Sleep for another expunge interval + + + s + expunge.interval + + + + + + sleep.sh + + Sleep for Storage CleanUp interval + + + s + storage.cleanup.interval + + + + + + sleep.sh + + Sleep for another Storage CleanUp interval + + + s + storage.cleanup.interval + + + + + + + + listvdi.sh + true + + List VDI for template on the xen host after vm using template is destroyed + + + h + vmhost_ipaddress + + + u + template_uuid + + + + + + + + deployVirtualMachine + Deploying virtual machine from the private template + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + privatetemplateid + + + account + accountname + + + domainid + 1 + + + displayname + VMcreatedfrmTemplate + + + group + group1 + + + networkids + networkid + + + + + id + vmid1 + + + hostid + vm_hostid + + + + + + listHosts + List host where vm is running + + + id + vm_hostid + + + + + ipaddress + vmhost_ipaddress + + + + + + select install_path from template_spool_ref + true + Getting private template vdi from the db + + + template_id + privatetemplateid + + + + + install_path + template_uuid + + + + + + listvdi.sh + + List VDI for template on the xen host before vm is destroyed + + + h + vmhost_ipaddress + + + u + template_uuid + + + + + + destroyVirtualMachine + Destroy virtual machine created from the private template + + + id + vmid1 + + + + + + + listConfigurations + Getting expunge interval value + + + name + expunge.interval + + + + + value + expunge.interval + + + + + + sleep.sh + + Sleep for expunge interval + + + s + expunge.interval + + + + + + + sleep.sh + + Sleep for another expunge interval + + + s + expunge.interval + + + + + + sleep.sh + + Sleep for Storage CleanUp interval + + + s + storage.cleanup.interval + + + + + + sleep.sh + + Sleep for another Storage CleanUp interval + + + s + storage.cleanup.interval + + + + + + + + listvdi.sh + true + + List VDI for template on the xen host after vm using template is destroyed + + + h + vmhost_ipaddress + + + u + template_uuid + + + + + + + + + startVirtualMachine + [Start virtual machine] + + + id + vmid + + + + + state + Running + + + + + + rebootVirtualMachine + [Reboot Virtual Machine] + + + id + vmid + + + + + + destroyVirtualMachine + [Destroy virtual machine] + + + id + vmid + + + + + + listVirtualMachines + [Verify that vm was destroyed] + + + id + vmid + + + + + state + Destroyed + + + + + + deleteTemplate + Delete private template + + + id + privatetemplateid + + + + + + deleteAccount + [Delete account] + + + id + accountid + + + + + + diff --git a/test/metadata/func/templates_sync.xml b/test/metadata/func/templates_sync.xml index 96d6362a7440..fdf9185eb784 100644 --- a/test/metadata/func/templates_sync.xml +++ b/test/metadata/func/templates_sync.xml @@ -1,1060 +1,1060 @@ - - - - - - - - - deployVirtualMachine - Deploy a VM - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - TempSyncVM - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - - - - select id from volumes - true - Getting volume id for the VM created - - - instance_id - vmid - - - - - id - volumeid - - - - - - stopVirtualMachine - Stop the Virtual Machine - - - id - vmid - - - - - - - createTemplate - Create template - - - volumeid - volumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - account - admin - - - domainid - 1 - - - - - id - privatetemplateid - - - - - listtemplate.sh - - List template directory after it was created - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - removetemplate.sh - - Removing template file from secondary storage - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - ms.sh - - Restarting management server - - - h - ms2 - - - o - restart - - - - - - - sleep.sh - - Sleep for 4 min - - - s - 240 - - - - - - - createTemplate - Create template - - - volumeid - volumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - account - admin - - - domainid - 1 - - - - - id - privatetemplateid - - - - - - listtemplate.sh - - List template directory after it was created - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - corrupttemplate.sh - - Corrupting template file on secondary storage - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - i - privatetemplateid - - - a - 2 - - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - ms.sh - - Restarting management server - - - h - ms2 - - - o - restart - - - - - - - sleep.sh - - Sleep for 4 min - - - s - 240 - - - - - - listtemplate.sh - - true - Verify that template got removed from the secondary storage by template sync - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - - - createTemplate - Create template - - - volumeid - volumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - account - admin - - - domainid - 1 - - - - - id - privatetemplateid - - - - - - listtemplate.sh - - List template directory after it was created - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - createfaketemplate.sh - - Corrupting template file on secondary storage - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - i - privatetemplateid - - - a - 1 - - - dir - 300556 - - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - ms.sh - - Restarting management server - - - h - ms2 - - - o - restart - - - - - - - sleep.sh - - Sleep for 4 min - - - s - 240 - - - - - - listtemplate.sh - - true - Verify that template got removed from the secondary storage by template sync - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - /template/tmpl/1/300556/template.properties - - - - - - - - createTemplate - Create template - - - volumeid - volumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - account - admin - - - domainid - 1 - - - - - id - privatetemplateid - - - - - - listtemplate.sh - - List template directory after it was created - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - removetemplate.sh - - Removing template file from secondary storage - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - - ms.sh - - Stopping management server - - - h - ms1 - - - o - stop - - - - - - ms.sh - - Stopping management server - - - h - ms2 - - - o - stop - - - - - - - ms.sh - - Starting management server - - - h - ms1 - - - o - start - - - - - - ms.sh - - Starting management server - - - h - ms2 - - - o - start - - - - - - sleep.sh - - Sleep for 4 min - - - s - 240 - - - - - - - createTemplate - Create template - - - volumeid - volumeid - - - name - privatetemplatename - - - displaytext - newtext - - - osTypeId - 12 - - - account - admin - - - domainid - 1 - - - - - id - privatetemplateid - - - - - - listtemplate.sh - - List template directory after it was created - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - ms.sh - - Restarting management server - - - h - ms1 - - - o - restart - - - - - - ms.sh - - Restarting management server - - - h - ms2 - - - o - restart - - - - - - - sleep.sh - - Sleep for 4 min - - - s - 240 - - - - - - deployVirtualMachine - Deploy vm from private template after management server was rebooted - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - privatetemplateid - - - account - admin - - - domainid - 1 - - - displayname - PrivateTempSyncVM - - - group - group2 - - - networkids - globalnetworkid - - - - - id - vmid - - - - - - - - registerTemplate - Register template - - - name - registername - - - displaytext - registerdisplay - - - url - globaltemplateurl - - - ostypeid - 12 - - - format - vhd - - - zoneid - globalzoneid - - - account - admin - - - domainid - 1 - - - - - id - privatetemplateid - - - - - - sleep.sh - - Sleep for 5 min - - - s - 300 - - - - - - listtemplate.sh - - List template directory after it was created - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - removetemplate.sh - - Removing template file from secondary storage - - - h - secondary_storage_ip - - - s - secondary_storage_dir - - - p - template_path - - - - - - - ms.sh - - Stopping management server - - - h - ms1 - - - o - stop - - - - - - ms.sh - - Stopping management server - - - h - ms2 - - - o - stop - - - - - - - ms.sh - - Starting management server - - - h - ms1 - - - o - start - - - - - - ms.sh - - Starting management server - - - h - ms2 - - - o - start - - - - - - sleep.sh - - Sleep for 15 seconds - - - s - 15 - - - - - - - destroyVirtualMachine - Destroy virtual machine as a part of cleanup - - - id - vmid - - - - - - + + + + + + + + + deployVirtualMachine + Deploy a VM + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + TempSyncVM + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + + + + select id from volumes + true + Getting volume id for the VM created + + + instance_id + vmid + + + + + id + volumeid + + + + + + stopVirtualMachine + Stop the Virtual Machine + + + id + vmid + + + + + + + createTemplate + Create template + + + volumeid + volumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + account + admin + + + domainid + 1 + + + + + id + privatetemplateid + + + + + listtemplate.sh + + List template directory after it was created + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + removetemplate.sh + + Removing template file from secondary storage + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + ms.sh + + Restarting management server + + + h + ms2 + + + o + restart + + + + + + + sleep.sh + + Sleep for 4 min + + + s + 240 + + + + + + + createTemplate + Create template + + + volumeid + volumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + account + admin + + + domainid + 1 + + + + + id + privatetemplateid + + + + + + listtemplate.sh + + List template directory after it was created + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + corrupttemplate.sh + + Corrupting template file on secondary storage + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + i + privatetemplateid + + + a + 2 + + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + ms.sh + + Restarting management server + + + h + ms2 + + + o + restart + + + + + + + sleep.sh + + Sleep for 4 min + + + s + 240 + + + + + + listtemplate.sh + + true + Verify that template got removed from the secondary storage by template sync + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + + + createTemplate + Create template + + + volumeid + volumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + account + admin + + + domainid + 1 + + + + + id + privatetemplateid + + + + + + listtemplate.sh + + List template directory after it was created + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + createfaketemplate.sh + + Corrupting template file on secondary storage + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + i + privatetemplateid + + + a + 1 + + + dir + 300556 + + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + ms.sh + + Restarting management server + + + h + ms2 + + + o + restart + + + + + + + sleep.sh + + Sleep for 4 min + + + s + 240 + + + + + + listtemplate.sh + + true + Verify that template got removed from the secondary storage by template sync + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + /template/tmpl/1/300556/template.properties + + + + + + + + createTemplate + Create template + + + volumeid + volumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + account + admin + + + domainid + 1 + + + + + id + privatetemplateid + + + + + + listtemplate.sh + + List template directory after it was created + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + removetemplate.sh + + Removing template file from secondary storage + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + + ms.sh + + Stopping management server + + + h + ms1 + + + o + stop + + + + + + ms.sh + + Stopping management server + + + h + ms2 + + + o + stop + + + + + + + ms.sh + + Starting management server + + + h + ms1 + + + o + start + + + + + + ms.sh + + Starting management server + + + h + ms2 + + + o + start + + + + + + sleep.sh + + Sleep for 4 min + + + s + 240 + + + + + + + createTemplate + Create template + + + volumeid + volumeid + + + name + privatetemplatename + + + displaytext + newtext + + + osTypeId + 12 + + + account + admin + + + domainid + 1 + + + + + id + privatetemplateid + + + + + + listtemplate.sh + + List template directory after it was created + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + ms.sh + + Restarting management server + + + h + ms1 + + + o + restart + + + + + + ms.sh + + Restarting management server + + + h + ms2 + + + o + restart + + + + + + + sleep.sh + + Sleep for 4 min + + + s + 240 + + + + + + deployVirtualMachine + Deploy vm from private template after management server was rebooted + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + privatetemplateid + + + account + admin + + + domainid + 1 + + + displayname + PrivateTempSyncVM + + + group + group2 + + + networkids + globalnetworkid + + + + + id + vmid + + + + + + + + registerTemplate + Register template + + + name + registername + + + displaytext + registerdisplay + + + url + globaltemplateurl + + + ostypeid + 12 + + + format + vhd + + + zoneid + globalzoneid + + + account + admin + + + domainid + 1 + + + + + id + privatetemplateid + + + + + + sleep.sh + + Sleep for 5 min + + + s + 300 + + + + + + listtemplate.sh + + List template directory after it was created + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + removetemplate.sh + + Removing template file from secondary storage + + + h + secondary_storage_ip + + + s + secondary_storage_dir + + + p + template_path + + + + + + + ms.sh + + Stopping management server + + + h + ms1 + + + o + stop + + + + + + ms.sh + + Stopping management server + + + h + ms2 + + + o + stop + + + + + + + ms.sh + + Starting management server + + + h + ms1 + + + o + start + + + + + + ms.sh + + Starting management server + + + h + ms2 + + + o + start + + + + + + sleep.sh + + Sleep for 15 seconds + + + s + 15 + + + + + + + destroyVirtualMachine + Destroy virtual machine as a part of cleanup + + + id + vmid + + + + + + diff --git a/test/metadata/func/userapi.xml b/test/metadata/func/userapi.xml index 67246cbb0bc5..0ee2d4885958 100644 --- a/test/metadata/func/userapi.xml +++ b/test/metadata/func/userapi.xml @@ -1,1293 +1,1293 @@ - - - - - - createAccount - [Create the Account and a User for the account] - - - account - accountname - - - accounttype - 1 - - - domainid - 1 - - - email - nimbus-user@gmail.com - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountname - - - - - - - listUsers - 5 [List the Created User in (3)] - - - account - accountname - - - - - id - userid - - - username - usernameparam - - - - - - - createUser - [Create the Same User Again Should Fail] - true - - - account - accountname - - - accounttype - 1 - - - username - usernameparam - - - password - password - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - domainid - 1 - - - - - - - - - - - - createAccount - 54 [Create a Different Account with an Existing User's Name] - true - - - account - - - accounttype - 1 - - - domainid - 1 - - - email - nimbus-user@gmail.com - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - username - usernameparam - - - password - password - - - - - - - - createUser - 593 [Create a Different User in the Same Account] - - - account - accountname - - - accounttype - 0 - - - username - - - password - password - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - email - nimbus-user@gmail.com - - - domainid - 1 - - - - - - id - seconduserid - - - - - updateUser - 593 - true - - - id - seconduserid - - - username - usernameparam - - - - - - - - deleteUser - 56 - true - - - id - 100000000 - - - - - - - deleteUser - - - id - userid - - - - - - deleteUser - 58 - true - - - id - userid - - - - - - - listUsers - 59 - true - - - id - userid - - - - - - - listUsers - 60 - true - - - id - 10000000 - - - - - - - updateUser - 62 - true - - - id - userid - - - username - - - - - - - updateUser - 63 - true - - - id - 2000000000 - - - username - - - - - - - createUser - 45 - true - - - username - - - password - john12345 - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - accounttype - 0 - - - domainid - 1000000 - - - - - - - createUser - 46 - - - username - - - password - john12345 - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - accounttype - 0 - - - account - accountname - - - domainid - 1 - - - - - - - - createUser - 47 - true - - - username - - - firstname - admin - - - lastname - adminlastname - - - accounttype - 0 - - - email - admin@gmail.com - - - - - - - createUser - 47 - true - - - username - - - password - 1234567 - - - lastname - adminlastname - - - accounttype - 0 - - - email - admin@gmail.com - - - - - - createUser - 47 - true - - - username - - - password - 1234567 - - - firstname - admin - - - email - admin@gmail.com - - - accounttype - 0 - - - - - - createUser - 47 - true - - - username - - - password - 1234567 - - - firstname - admin - - - lastname - adminlastname - - - accounttype - 0 - - - - - - - - createDomain - - - name - - - - - name - domainname - - - id - newdomainid - - - - - - createAccount - [Create the Account and a User in the New Domain] - - - account - accountname1 - - - accounttype - 2 - - - domainid - newdomainid - - - email - nimbus-user@gmail.com - - - firstname - nimbus-user - - - lastname - nimbus-userlastname - - - username - usernameparam - - - password - password - - - - - id - accountid - - - name - accountname1 - - - - - - - listUsers - 52 [List the Created User in (3)] - - - account - accountname1 - - - - - id - userid - - - username - usernameparam - - - - - - - - createUser - 51 - true - - - username - - - password - password - - - firstname - alena - - - lastname - alenaslastname - - - email - alena@gmail.com - - - accounttype - 0 - - - domainid - 300000000 - - - account - accountname1 - - - - - - - - createUser - - - username - - - password - jane - - - firstname - jane - - - lastname - jane - - - email - jane@gmail.com - - - accounttype - 0 - - - account - accountname - - - domainid - newdomainid - - - - - - createUser - - - username - - - password - jane - - - firstname - jane - - - lastname - jane - - - email - jane@gmail.com - - - accounttype - 0 - - - account - accountname1 - - - domainid - newdomainid - - - - - - - createDomain - - - name - - - - - id - domaindomainid - - - name - domaindomainname - - - - - - createDomain - 64 - true - - - name - domaindomainname - - - - - - - - deleteDomain - - - id - domaindomainid - - - - - - listDomains - 65 - true - - - name - domaindomainname - - - - - - - - - deleteDomain - 66 - true - - - domainid - newdomainid - - - - - - - - - listDomains - 140 - - - name - root - - - - - - - - - createUser - - - username - - - password - testcase601 - - - firstname - admin - - - lastname - adminlastname - - - email - admin@gmail.com - - - accounttype - 0 - - - account - accountname - - - - - id - userid601 - - - username - username601 - - - account - account601 - - - - - - deployVirtualMachine - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - account601 - - - domainid - 1 - - - displayname - DomainTest-VM - - - group - group1 - - - networkids - networkid - - - - - - deleteUser - - - id - userid601 - - - - - - listEvents - 601 - - - account - account601 - - - - - - - - createUser - - - username - - - password - baduser - - - firstname - configuser - - - lastname - adminlastname - - - email - configuser@gmail.com - - - accounttype - 0 - - - account - accountname - - - domainid - 1 - - - - - id - baduserid - - - username - badusername - - - - - - registerUserKeys - - - id - baduserid - - - - - apikey - apikey - - - secretkey - secretkey - - - - - - listVirtualMachines - true - 75 - true - - - account - admin - - - - - - - - deleteDomain - - - id - newdomainid - - - cleanup - true - - - - - - - deleteAccount - - - id - accountid - - - - - - deleteAccount - - - id - accountid1 - - - - - + + + + + + createAccount + [Create the Account and a User for the account] + + + account + accountname + + + accounttype + 1 + + + domainid + 1 + + + email + nimbus-user@gmail.com + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountname + + + + + + + listUsers + 5 [List the Created User in (3)] + + + account + accountname + + + + + id + userid + + + username + usernameparam + + + + + + + createUser + [Create the Same User Again Should Fail] + true + + + account + accountname + + + accounttype + 1 + + + username + usernameparam + + + password + password + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + domainid + 1 + + + + + + + + + + + + createAccount + 54 [Create a Different Account with an Existing User's Name] + true + + + account + + + accounttype + 1 + + + domainid + 1 + + + email + nimbus-user@gmail.com + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + username + usernameparam + + + password + password + + + + + + + + createUser + 593 [Create a Different User in the Same Account] + + + account + accountname + + + accounttype + 0 + + + username + + + password + password + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + email + nimbus-user@gmail.com + + + domainid + 1 + + + + + + id + seconduserid + + + + + updateUser + 593 + true + + + id + seconduserid + + + username + usernameparam + + + + + + + + deleteUser + 56 + true + + + id + 100000000 + + + + + + + deleteUser + + + id + userid + + + + + + deleteUser + 58 + true + + + id + userid + + + + + + + listUsers + 59 + true + + + id + userid + + + + + + + listUsers + 60 + true + + + id + 10000000 + + + + + + + updateUser + 62 + true + + + id + userid + + + username + + + + + + + updateUser + 63 + true + + + id + 2000000000 + + + username + + + + + + + createUser + 45 + true + + + username + + + password + john12345 + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + accounttype + 0 + + + domainid + 1000000 + + + + + + + createUser + 46 + + + username + + + password + john12345 + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + accounttype + 0 + + + account + accountname + + + domainid + 1 + + + + + + + + createUser + 47 + true + + + username + + + firstname + admin + + + lastname + adminlastname + + + accounttype + 0 + + + email + admin@gmail.com + + + + + + + createUser + 47 + true + + + username + + + password + 1234567 + + + lastname + adminlastname + + + accounttype + 0 + + + email + admin@gmail.com + + + + + + createUser + 47 + true + + + username + + + password + 1234567 + + + firstname + admin + + + email + admin@gmail.com + + + accounttype + 0 + + + + + + createUser + 47 + true + + + username + + + password + 1234567 + + + firstname + admin + + + lastname + adminlastname + + + accounttype + 0 + + + + + + + + createDomain + + + name + + + + + name + domainname + + + id + newdomainid + + + + + + createAccount + [Create the Account and a User in the New Domain] + + + account + accountname1 + + + accounttype + 2 + + + domainid + newdomainid + + + email + nimbus-user@gmail.com + + + firstname + nimbus-user + + + lastname + nimbus-userlastname + + + username + usernameparam + + + password + password + + + + + id + accountid + + + name + accountname1 + + + + + + + listUsers + 52 [List the Created User in (3)] + + + account + accountname1 + + + + + id + userid + + + username + usernameparam + + + + + + + + createUser + 51 + true + + + username + + + password + password + + + firstname + alena + + + lastname + alenaslastname + + + email + alena@gmail.com + + + accounttype + 0 + + + domainid + 300000000 + + + account + accountname1 + + + + + + + + createUser + + + username + + + password + jane + + + firstname + jane + + + lastname + jane + + + email + jane@gmail.com + + + accounttype + 0 + + + account + accountname + + + domainid + newdomainid + + + + + + createUser + + + username + + + password + jane + + + firstname + jane + + + lastname + jane + + + email + jane@gmail.com + + + accounttype + 0 + + + account + accountname1 + + + domainid + newdomainid + + + + + + + createDomain + + + name + + + + + id + domaindomainid + + + name + domaindomainname + + + + + + createDomain + 64 + true + + + name + domaindomainname + + + + + + + + deleteDomain + + + id + domaindomainid + + + + + + listDomains + 65 + true + + + name + domaindomainname + + + + + + + + + deleteDomain + 66 + true + + + domainid + newdomainid + + + + + + + + + listDomains + 140 + + + name + root + + + + + + + + + createUser + + + username + + + password + testcase601 + + + firstname + admin + + + lastname + adminlastname + + + email + admin@gmail.com + + + accounttype + 0 + + + account + accountname + + + + + id + userid601 + + + username + username601 + + + account + account601 + + + + + + deployVirtualMachine + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + account601 + + + domainid + 1 + + + displayname + DomainTest-VM + + + group + group1 + + + networkids + networkid + + + + + + deleteUser + + + id + userid601 + + + + + + listEvents + 601 + + + account + account601 + + + + + + + + createUser + + + username + + + password + baduser + + + firstname + configuser + + + lastname + adminlastname + + + email + configuser@gmail.com + + + accounttype + 0 + + + account + accountname + + + domainid + 1 + + + + + id + baduserid + + + username + badusername + + + + + + registerUserKeys + + + id + baduserid + + + + + apikey + apikey + + + secretkey + secretkey + + + + + + listVirtualMachines + true + 75 + true + + + account + admin + + + + + + + + deleteDomain + + + id + newdomainid + + + cleanup + true + + + + + + + deleteAccount + + + id + accountid + + + + + + deleteAccount + + + id + accountid1 + + + + + diff --git a/test/metadata/func/vmapi.xml b/test/metadata/func/vmapi.xml index fbda66fe5d7f..e4ac392c28aa 100644 --- a/test/metadata/func/vmapi.xml +++ b/test/metadata/func/vmapi.xml @@ -1,891 +1,891 @@ - - - - - - - - - deployVirtualMachine - [Deploy Virtual Machine in Admin Account] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - VMapis-VM - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid - - - name - vmname - - - ipaddress - vmipaddress - - - - - - destroyVirtualMachine - [Destroy Virtual Machine] - - - id - vmid - - - - - - listVirtualMachines - 72 [List the Destroyed Virutal Machine] - - - account - admin - - - name - vmname - - - - - - - startVirtualMachine - 92 [Check that the Destroyed VM cannot be started] - true - - - id - vmid - - - - - - - listVirtualMachines - 73 [Check that a non-existing VM cannot be listed (Id doesn't exist)] - - - account - admin - - - id - 100000000 - - - - - - - - createAccount - [Create the Account and a User for the account (List VM for a Removed User exercise BEGIN)] - - - account - accountname - - - accounttype - 0 - - - domainid - 1 - - - email - testcase74@gmail.com - - - firstname - testcase74 - - - lastname - testcase74lastname - - - username - - - password - password - - - - - id - accountid - - - name - accountname - - - - - - - listUsers - [List the Created User] - - - account - accountname - - - - - - id - userid - - - username - usernameparam - - - - - - - - - createNetwork - [Creating default network for the account] - - - zoneid - globalzoneid - - - networkOfferingId - globalnetworkofferingid - - - account - accountname - - - domainid - 1 - - - name - vmapi_network - - - displaytext - vmapi_network - - - - - id - networkid - - - - - - deployVirtualMachine - [Deploy Virtual Machine in the new Account] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - VMapis-VM-2 - - - group - group1 - - - networkids - networkid - - - - - id - vmid2 - - - name - vmname - - - ipaddress - vmipaddress - - - - - - deleteUser - [Delete User] - - - id - userid - - - - - - deleteAccount - [Delete Account] - - - id - accountid - - - - - - listVirtualMachines - 74 [Check that no VMs present under the Account(List VM for a Removed User exercise END)] - true - - - account - accountname - - - domainid - 1 - - - - - - - updateVirtualMachine - 77 - Bug 3237 [updateVirtualMachine api: should deny to update Destroyed vms] - true - - - id - vmid - - - haenable - true - - - - - - - updateVirtualMachine - 592 [Verify that you can't enable HA for non-existing vm] - true - - - id - 1000000 - - - haenable - true - - - - - - - - recoverVirtualMachine - [Recover the Destroyed VM] - - - id - vmid - - - - - - resetPasswordForVirtualMachine - 79-1 [Verify that one can reset password for VM in stopped state] - - - id - vmid - - - - - - - - updateVirtualMachine - 76 [Verify that one can enable HA for VM in stopped state] - - - id - vmid - - - haenable - true - - - - - - - - startVirtualMachine - [Start Virtual Machine (reset password for VM in running state exercise BEGIN)] - - - id - vmid - - - - - - resetPasswordForVirtualMachine - 79-2 [Verify that one can reset password for VM in running state] - - - id - vmid - - - - - - - - updateVirtualMachine - 76 [Verify that one can disable HA for VM in running state] - - - id - vmid - - - haenable - false - - - - - - - - destroyVirtualMachine - [Destroy Virtual Machine (reset password on a destroyed VM exercise BEGIN)] - - - id - vmid - - - - - - resetPasswordForVirtualMachine - 80 [Verify that one can't reset password for VM in destroyed state] - true - - - id - vmid - - - - - - - rebootVirtualMachine - 89 [Verify that one can't reboot destroyed VM] - true - - - id - vmid - - - - - - - - deployVirtualMachine - [Deploy Virtual Machine to test reset password negative case] - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - admin - - - domainid - 1 - - - displayname - VMapis-VM-3 - - - group - group1 - - - networkids - networkid - - - - - id - windowsvmid - - - name - windowsvmname - - - ipaddress - windowsvmipaddress - - - - - - resetPasswordForVirtualMachine - 81 [Check whether password can be reset on virtual machine made from password disabled VM] - true - - - id - windowsvmid - - - - - - - changeServiceForVirtualMachine - 82 [Try to change service offering for VM in running state] - true - - - id - windowsvmid - - - serviceofferingid - mediumglobalserviceofferingid - - - - - - - stopVirtualMachine - [Stop the Virtual machine to change its Service Offering] - - - id - windowsvmid - - - - - - changeServiceForVirtualMachine - 83 [Try to change service offering to the one that VM already has -ve case] - true - - - id - windowsvmid - - - serviceofferingid - globalserviceofferingid - - - - - - - - rebootVirtualMachine - 90 [Try to reboot stopped virtual machine - Negative case] - true - - - id - windowsvmid - - - - - - - - recoverVirtualMachine - 813 [Try to recover stopped virtual machine] - true - - - id - windowsvmid - - - - - - startVirtualMachine - [Start virtual machine] - - - id - windowsvmid - - - - - - recoverVirtualMachine - 813 [Try to recover running virtual machine] - true - - - id - windowsvmid - - - - - - - - deployVirtualMachine - 84 [Try to deploy vm and assign it to non-existing account -ve case] - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - blahblah-invaliduser - - - domainid - 1 - - - displayname - VMapis-VM-4 - - - group - group1 - - - networkids - globalnetworkid - - - - - - - deployVirtualMachine - 85 [Try to deploy vm and assign it to removed account -ve case] - true - - - zoneid - globalzoneid - - - serviceofferingid - globalserviceofferingid - - - diskofferingid - globaldiskofferingid - - - templateid - globaltemplateid - - - account - accountname - - - domainid - 1 - - - displayname - VMapis-VM-5 - - - group - group1 - - - networkids - networkid - - - - - - - destroyVirtualMachine - 87 [Try to send destroy command for non-existing vm -ve case] - true - - - id - 100000 - - - - - - - rebootVirtualMachine - 88 [Try to send reboot command for non-existing vm -ve case] - true - - - id - 100000 - - - - - - - startVirtualMachine - 88 [Try to send start command for non-existing vm -ve case] - true - - - id - 100000 - - - - - - - destroyVirtualMachine - [Destroy Virtual Machine as part of Clean up] - - - id - windowsvmid - - - - - - + + + + + + + + + deployVirtualMachine + [Deploy Virtual Machine in Admin Account] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + VMapis-VM + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid + + + name + vmname + + + ipaddress + vmipaddress + + + + + + destroyVirtualMachine + [Destroy Virtual Machine] + + + id + vmid + + + + + + listVirtualMachines + 72 [List the Destroyed Virutal Machine] + + + account + admin + + + name + vmname + + + + + + + startVirtualMachine + 92 [Check that the Destroyed VM cannot be started] + true + + + id + vmid + + + + + + + listVirtualMachines + 73 [Check that a non-existing VM cannot be listed (Id doesn't exist)] + + + account + admin + + + id + 100000000 + + + + + + + + createAccount + [Create the Account and a User for the account (List VM for a Removed User exercise BEGIN)] + + + account + accountname + + + accounttype + 0 + + + domainid + 1 + + + email + testcase74@gmail.com + + + firstname + testcase74 + + + lastname + testcase74lastname + + + username + + + password + password + + + + + id + accountid + + + name + accountname + + + + + + + listUsers + [List the Created User] + + + account + accountname + + + + + + id + userid + + + username + usernameparam + + + + + + + + + createNetwork + [Creating default network for the account] + + + zoneid + globalzoneid + + + networkOfferingId + globalnetworkofferingid + + + account + accountname + + + domainid + 1 + + + name + vmapi_network + + + displaytext + vmapi_network + + + + + id + networkid + + + + + + deployVirtualMachine + [Deploy Virtual Machine in the new Account] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + VMapis-VM-2 + + + group + group1 + + + networkids + networkid + + + + + id + vmid2 + + + name + vmname + + + ipaddress + vmipaddress + + + + + + deleteUser + [Delete User] + + + id + userid + + + + + + deleteAccount + [Delete Account] + + + id + accountid + + + + + + listVirtualMachines + 74 [Check that no VMs present under the Account(List VM for a Removed User exercise END)] + true + + + account + accountname + + + domainid + 1 + + + + + + + updateVirtualMachine + 77 - Bug 3237 [updateVirtualMachine api: should deny to update Destroyed vms] + true + + + id + vmid + + + haenable + true + + + + + + + updateVirtualMachine + 592 [Verify that you can't enable HA for non-existing vm] + true + + + id + 1000000 + + + haenable + true + + + + + + + + recoverVirtualMachine + [Recover the Destroyed VM] + + + id + vmid + + + + + + resetPasswordForVirtualMachine + 79-1 [Verify that one can reset password for VM in stopped state] + + + id + vmid + + + + + + + + updateVirtualMachine + 76 [Verify that one can enable HA for VM in stopped state] + + + id + vmid + + + haenable + true + + + + + + + + startVirtualMachine + [Start Virtual Machine (reset password for VM in running state exercise BEGIN)] + + + id + vmid + + + + + + resetPasswordForVirtualMachine + 79-2 [Verify that one can reset password for VM in running state] + + + id + vmid + + + + + + + + updateVirtualMachine + 76 [Verify that one can disable HA for VM in running state] + + + id + vmid + + + haenable + false + + + + + + + + destroyVirtualMachine + [Destroy Virtual Machine (reset password on a destroyed VM exercise BEGIN)] + + + id + vmid + + + + + + resetPasswordForVirtualMachine + 80 [Verify that one can't reset password for VM in destroyed state] + true + + + id + vmid + + + + + + + rebootVirtualMachine + 89 [Verify that one can't reboot destroyed VM] + true + + + id + vmid + + + + + + + + deployVirtualMachine + [Deploy Virtual Machine to test reset password negative case] + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + admin + + + domainid + 1 + + + displayname + VMapis-VM-3 + + + group + group1 + + + networkids + networkid + + + + + id + windowsvmid + + + name + windowsvmname + + + ipaddress + windowsvmipaddress + + + + + + resetPasswordForVirtualMachine + 81 [Check whether password can be reset on virtual machine made from password disabled VM] + true + + + id + windowsvmid + + + + + + + changeServiceForVirtualMachine + 82 [Try to change service offering for VM in running state] + true + + + id + windowsvmid + + + serviceofferingid + mediumglobalserviceofferingid + + + + + + + stopVirtualMachine + [Stop the Virtual machine to change its Service Offering] + + + id + windowsvmid + + + + + + changeServiceForVirtualMachine + 83 [Try to change service offering to the one that VM already has -ve case] + true + + + id + windowsvmid + + + serviceofferingid + globalserviceofferingid + + + + + + + + rebootVirtualMachine + 90 [Try to reboot stopped virtual machine - Negative case] + true + + + id + windowsvmid + + + + + + + + recoverVirtualMachine + 813 [Try to recover stopped virtual machine] + true + + + id + windowsvmid + + + + + + startVirtualMachine + [Start virtual machine] + + + id + windowsvmid + + + + + + recoverVirtualMachine + 813 [Try to recover running virtual machine] + true + + + id + windowsvmid + + + + + + + + deployVirtualMachine + 84 [Try to deploy vm and assign it to non-existing account -ve case] + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + blahblah-invaliduser + + + domainid + 1 + + + displayname + VMapis-VM-4 + + + group + group1 + + + networkids + globalnetworkid + + + + + + + deployVirtualMachine + 85 [Try to deploy vm and assign it to removed account -ve case] + true + + + zoneid + globalzoneid + + + serviceofferingid + globalserviceofferingid + + + diskofferingid + globaldiskofferingid + + + templateid + globaltemplateid + + + account + accountname + + + domainid + 1 + + + displayname + VMapis-VM-5 + + + group + group1 + + + networkids + networkid + + + + + + + destroyVirtualMachine + 87 [Try to send destroy command for non-existing vm -ve case] + true + + + id + 100000 + + + + + + + rebootVirtualMachine + 88 [Try to send reboot command for non-existing vm -ve case] + true + + + id + 100000 + + + + + + + startVirtualMachine + 88 [Try to send start command for non-existing vm -ve case] + true + + + id + 100000 + + + + + + + destroyVirtualMachine + [Destroy Virtual Machine as part of Clean up] + + + id + windowsvmid + + + + + + diff --git a/test/metadata/func/vmsync.xml b/test/metadata/func/vmsync.xml index c0b34c9c37a7..048e104ffe6a 100644 --- a/test/metadata/func/vmsync.xml +++ b/test/metadata/func/vmsync.xml @@ -1,407 +1,407 @@ - - - - - - - deployVirtualMachine - deploy HA enabled VM test case - - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - displayname - HAenabledVM - - - account - admin - - - domainid - 1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid_en - - - name - vmname_en - - - hostid - hostid_en - - - - - - - deployVirtualMachine - deploy HA disabled VM test case - - - - diskofferingid - globaldiskofferingid - - - serviceofferingid - globalserviceofferingid - - - templateid - globaltemplateid - - - zoneid - globalzoneid - - - displayname - HAdisabledVM - - - account - admin - - - domainid - 1 - - - group - group1 - - - networkids - globalnetworkid - - - - - id - vmid_db - - - name - vmname_db - - - hostid - hostid_db - - - - - - - - updateVirtualMachine - HA enable virtual machine - - - id - vmid_en - - - displayname - HAenabledVirtualMachine - - - haenable - true - - - - - - - updateVirtualMachine - HA disable virtual machine - - - id - vmid_db - - - displayname - HAdisabledVirtualMachine - - - haenable - false - - - - - - - - listHosts - Obtain IP address of ha disabled VM's Host Test Case - - - id - hostid_db - - - - - ipaddress - ipaddress_db - - - - - - - - listHosts - Obtain IP address of ha enabled VM's Host Test Case - - - id - hostid_en - - - - - ipaddress - ipaddress_en - - - - - - - select instance_name from vm_instance - true - Obtaining the internal instance name for the ha disabled VM Test Case - - - id - vmid_db - - - - - instance_name - instance_name_db - - - - - - select instance_name from vm_instance - true - Obtaining the internal instance name for the ha enabled VM Test Case - - - id - vmid_en - - - - - instance_name - instance_name_en - - - - - - - - killvm.sh - Kill the HA enabled VM - - - - - n - instance_name_en - - - - h - ipaddress_en - - - - - - - killvm.sh - Kill the HA disabled VM - - - - - n - instance_name_db - - - - h - ipaddress_db - - - - - - - sleep.sh - - Sleep for some time - - - s - 300 - - - - - - - - - listVirtualMachines - List virtual machine with ha disabled - - - id - vmid_db - - - - - state - Stopped - - - - - - listVirtualMachines - List virtual machine with ha enabled - - - id - vmid_en - - - - - state - Running - - - hostid - hostid_en - - - - - - - listHosts - List host with ha enabled machine - - - id - hostid_en - - - - - ipaddress - ipaddress_en - - - - - - - listvm.sh - - List ha enabled machine - - - h - ipaddress_en - - - n - instance_name_en - - - - - - - - destroyVirtualMachine - Destroy ha disabled vm as a part of cleanup - - - id - vmid_db - - - - - - destroyVirtualMachine - Destroy ha enabled vm as a part of cleanup - - - id - vmid_en - - - - + + + + + + + deployVirtualMachine + deploy HA enabled VM test case + + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + displayname + HAenabledVM + + + account + admin + + + domainid + 1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid_en + + + name + vmname_en + + + hostid + hostid_en + + + + + + + deployVirtualMachine + deploy HA disabled VM test case + + + + diskofferingid + globaldiskofferingid + + + serviceofferingid + globalserviceofferingid + + + templateid + globaltemplateid + + + zoneid + globalzoneid + + + displayname + HAdisabledVM + + + account + admin + + + domainid + 1 + + + group + group1 + + + networkids + globalnetworkid + + + + + id + vmid_db + + + name + vmname_db + + + hostid + hostid_db + + + + + + + + updateVirtualMachine + HA enable virtual machine + + + id + vmid_en + + + displayname + HAenabledVirtualMachine + + + haenable + true + + + + + + + updateVirtualMachine + HA disable virtual machine + + + id + vmid_db + + + displayname + HAdisabledVirtualMachine + + + haenable + false + + + + + + + + listHosts + Obtain IP address of ha disabled VM's Host Test Case + + + id + hostid_db + + + + + ipaddress + ipaddress_db + + + + + + + + listHosts + Obtain IP address of ha enabled VM's Host Test Case + + + id + hostid_en + + + + + ipaddress + ipaddress_en + + + + + + + select instance_name from vm_instance + true + Obtaining the internal instance name for the ha disabled VM Test Case + + + id + vmid_db + + + + + instance_name + instance_name_db + + + + + + select instance_name from vm_instance + true + Obtaining the internal instance name for the ha enabled VM Test Case + + + id + vmid_en + + + + + instance_name + instance_name_en + + + + + + + + killvm.sh + Kill the HA enabled VM + + + + + n + instance_name_en + + + + h + ipaddress_en + + + + + + + killvm.sh + Kill the HA disabled VM + + + + + n + instance_name_db + + + + h + ipaddress_db + + + + + + + sleep.sh + + Sleep for some time + + + s + 300 + + + + + + + + + listVirtualMachines + List virtual machine with ha disabled + + + id + vmid_db + + + + + state + Stopped + + + + + + listVirtualMachines + List virtual machine with ha enabled + + + id + vmid_en + + + + + state + Running + + + hostid + hostid_en + + + + + + + listHosts + List host with ha enabled machine + + + id + hostid_en + + + + + ipaddress + ipaddress_en + + + + + + + listvm.sh + + List ha enabled machine + + + h + ipaddress_en + + + n + instance_name_en + + + + + + + + destroyVirtualMachine + Destroy ha disabled vm as a part of cleanup + + + id + vmid_db + + + + + + destroyVirtualMachine + Destroy ha enabled vm as a part of cleanup + + + id + vmid_en + + + + diff --git a/test/pom.xml b/test/pom.xml index fe9e72170b32..7f315bb92158 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -24,7 +24,7 @@ org.apache.cloudstack cloudstack - 4.20.4.0-SNAPSHOT + 4.23.0.0-SNAPSHOT @@ -44,16 +44,6 @@ org.jenkins-ci trilead-ssh2 - - org.openqa.selenium.server - selenium-server - ${cs.selenium.server.version} - - - org.seleniumhq.selenium.client-drivers - selenium-java-client-driver - ${cs.selenium-java-client-driver.version} - compile diff --git a/test/scripts/deploycluster.sh b/test/scripts/deploycluster.sh index dbd2c7b606c8..f012efc8ebbd 100755 --- a/test/scripts/deploycluster.sh +++ b/test/scripts/deploycluster.sh @@ -60,7 +60,7 @@ deploy_server() { if [ $? -gt 0 ]; then echo "failed to setup db.properties file on remote $1"; return 2; fi #ssh root@$1 "cd /root/cloudstack-oss && nohup ant run &" - #if [ $? -gt 0 ]; then echo "failed to start the softare on remote $1"; return 2; fi + #if [ $? -gt 0 ]; then echo "failed to start the software on remote $1"; return 2; fi echo "Remote management server is deployed as a part of cluster setup; you have to start it manually by logging in remotely" } diff --git a/test/scripts/run.bat b/test/scripts/run.bat index a21851ddf2d2..f0cb9ff00695 100644 --- a/test/scripts/run.bat +++ b/test/scripts/run.bat @@ -5,9 +5,9 @@ rem regarding copyright ownership. The ASF licenses this file rem to you under the Apache License, Version 2.0 (the rem "License"); you may not use this file except in compliance rem with the License. You may obtain a copy of the License at -rem +rem rem http://www.apache.org/licenses/LICENSE-2.0 -rem +rem rem Unless required by applicable law or agreed to in writing, rem software distributed under the License is distributed on an rem "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY diff --git a/test/selenium/ReadMe.txt b/test/selenium/ReadMe.txt deleted file mode 100644 index bc968f1dc937..000000000000 --- a/test/selenium/ReadMe.txt +++ /dev/null @@ -1,66 +0,0 @@ -############################################## - -Questions? Post'em @ dev@cloudstack.apache.org - -############################################## - -This files contains following: - -1) Installation requirements -2) Testing pre-requisites -3) Running the Tests and Generating the report -############################################## - - - -########################################################################################################################################## - -1) Installation Requirements ---------------------------- - - -1) Firefox depending on your OS (Good to have Firebug and Selenium IDE for troubleshooting and dev work) - - -2) Install Python 2.7. - - -3) Now Open CMD/Terminal and type all of following - -- pip install pycrypto (Installs Pycrypto) -- pip install paramiko (Install paramiko) -- pip install unittest-xml-reporting (Install XML Test Runner) -- pip install -U selenium (Installs Selenium) - -4) Get PhoantomJS for your OS from http://phantomjs.org/ - -- PhantomJS will run selenium test in headless mode. Follow the instruction on PhantomJS.org. -- Make sure the executable is in PATH. (TIP: Drop it in Python27 folder :-)) - -5) Now get the HTMLTestRunner for nice looking report generation. -- http://tungwaiyip.info/software/HTMLTestRunner.html -- Download and put this file into Lib of your python installation. - - -########################################################################################################################################## - -2) Test Prerequisites ---------------------- - -- Download and install CS. /cwiki.apache.org has links to Installation Guide and API reference. -- Log into the management server and Add a Zone. (Must be Advance Zone and Hypervisor type must be Xen) - - -########################################################################################################################################## - -3) Running the Test and Generating the report ---------------------------------------------- - -- Folder smoke contains main.py -- main.py is the file where all the tests are serialized. -- main.py supports HTML and XML reporting. Please refer to end of file to choose either. -- Typical usage is: python main.py 10.1.1.10 >> result.xml for XML Reporting -- And python main.py 10.1.1.10 >> result.html for HTML Reporting. -- 10.1.1.10 (your management server IP) is an argument required for main. - -########################################################################################################################################## diff --git a/test/selenium/browser/firefox.py b/test/selenium/browser/firefox.py deleted file mode 100644 index e4e8e1c6262b..000000000000 --- a/test/selenium/browser/firefox.py +++ /dev/null @@ -1,55 +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. - -from selenium import webdriver -import time -from selenium.common.exceptions import WebDriverException -from selenium.common.exceptions import NoSuchElementException - -class Firefox(object): - def __init__(self, x_pos = 0, y_pos = 0, x_size = 1024, y_size = 768, timeout = 30): - self.browser = None - self.browser = webdriver.Firefox() - self.browser.set_page_load_timeout(timeout) - self.browser.set_window_position(x_pos, y_pos) - self.browser.set_window_size(x_size, y_size) - - def get_browser(self): - return self.browser - - def set_url(self, url): - if url == None or url == "": - print "A valid url is required" - return - self.url = url - self.browser.get(url) - - def quit_browser(self): - try: - self.browser.quit() - except NoSuchElementException as err: - print "Element error({0})".format(err.msg) - except WebDriverException as err: - print "WebDriver error({0})".format(err.msg) - - -if __name__ == "__main__": - # Create a new instance of the Firefox driver - browser = Firefox("Firefox") - browser.set_url("http://10.88.90.84:8080/client/") - time.sleep(3) - browser.quit_browser() diff --git a/test/selenium/common/Global_Locators.py b/test/selenium/common/Global_Locators.py deleted file mode 100644 index 0219f3b2e7bd..000000000000 --- a/test/selenium/common/Global_Locators.py +++ /dev/null @@ -1,230 +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. - -''' -Variable Names are as follows -Logical Page Descriptor_____What Element Represents and/or where it is_____LocatorType - - -For Example :: - -instances_xpath = "//div[@id='navigation']/ul/li[2]/span[2]" - -Means this is:: xpath link for Instances which is present on Dashboard. -Any test cases that requires to go into Instances from Dashboard can use this variable now. - -This may not be intuitive as you go deep into the tree. - - - -for example - -stopinstanceforce_id - -The best way to know what this represents is to track by variable name -Under Instances / any instance is click on any instance (applies to any instance) / stop instance has a force stop check box when you click. -This link represents that. - - -Steps below do not have global locators. - -PF rule steps including and after filling port numbers. (Refer to vmLifeAndNetwork.py / def test_PF) -FW rule steps including and after filling port numbers. (Refer to vmLifeAndNetwork.py / def test_PF) -ADD Disk Offering page has Names, description, storage type etc etc -ADD Compute Offering page has Names, description, CPU Cores, CPU clocks type etc etc - -Create Acc, Delete Acc, Login and Logout are for test flow and are not test cases. They do not have global Locators. - -Such and many more data entry points that appear only once and hence we do not need glonal names for them. They are hard coded as and when needed in the scripts. - - -''' - -################################################################################################################################################################################################ - -## Links on the Main UI page (Dash board). Listed in the order they appear on screen -dashboard_xpath = "//div[@id='navigation']/ul/li" -instances_xpath = "//div[@id='navigation']/ul/li[2]/span[2]" # Link for Instance and following as self explanatory -storage_xpath = "//div[@id='navigation']/ul/li[3]/span[2]" -network_xpath = "//div[@id='navigation']/ul/li[4]/span[2]" -templates_xpath = "//div[@id='navigation']/ul/li[5]/span[2]" -events_xpath = "//div[@id='navigation']/ul/li[6]/span[2]" -projects_xpath = "//div[@id='navigation']/ul/li[7]/span[2]" -accounts_xpath = "//div[@id='navigation']/ul/li[8]/span[2]" -domains_xpath = "//div[@id='navigation']/ul/li[9]/span[2]" -infrastructure_xpath = "//div[@id='navigation']/ul/li[10]/span[2]" -globalSettings_xpath = "//div[@id='navigation']/ul/li[11]/span[2]" -serviceOfferings_xpath = "//div[@id='navigation']/ul/li[12]/span[2]" - -################################################################################################################################################################################################ - -## Instances Page -## Instances Main page - - -# Add Instance Button on top right corner of Instances page -add_instance_xpath = "//div[2]/div/div[2]/div/div[2]/span" - -# Add Instance Wizard next button -add_instance_next_xpath = "//div[4]/div[2]/div[3]/div[3]/span" - -# Table that lists all VM's under Instances page; General usage is to traverse through this table and search for the VM we are interested in. -instances_table_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/table/tbody/tr/td/span" - - -# Click any instance and following are available - -# Click ok on confirmation pop-up box for most actions listed below -actionconfirm_xpath = ("//button[@type='button']") - -# status of VM running. Click on VM > 3rd row in table -state_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div[2]/div[2]/div/div/div[2]/div/table/tbody/tr[3]/td[2]/span" - -# Stop instance icon -stopinstance_css = "a[alt=\"Stop Instance\"] > span.icon" - -# stop instance forcefully check box available after stop instance is executed in separate pop up -stopinstanceforce_id = ("force_stop") - -# start instance icon -startinstance_css = "a[alt=\"Start Instance\"] > span.icon" - -yesconfirmation_xapth = "(//button[@type='button'])[2]" - - -# Destroy instance icon -destroyinstance_css = "a[alt=\"Destroy Instance\"] > span.icon" - -#Restore Instance icon -restoreinstance_css = "a[alt=\"Restore Instance\"] > span.icon" - -# Reboot instance -rebootinstance_css = "a[alt=\"Reboot Instance\"] > span.icon" - -################################################################################################################################################################################################ - - -## Network Page - -# Table that lists all Networks under Network page; General usage is to traverse through this table and search for the network we are interested in. -network_networktable_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div[3]/div[2]/div/div[2]/table/tbody/tr/td/span" - -# View IP addresses button on each network page -viewIp_css="div.view-all > a > span" - -# Acquire a new ip -acquireIP_xpath="//div[2]/div/div/div[2]/span" -# List of IP's within a netork table -network_iptables_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div[3]/div[2]/div/div[2]/table/tbody/tr/td/span" -# Configuration tab for each IP -ipConfiguration_text="Configuration" -# PF under configuration for each IP -ip_PF = "li.portForwarding > div.view-details" - - -################################################################################################################################################################################################ - - -## Servivce Offering Page - -# Selects Compute offering from drop down menu -Offering_compute_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[1]" - -# Selects System offering from drop down menu -Offering_system_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[2]" - -# Selects Disk offering from drop down menu -Offering_disk_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[3]" - -# Selects Network offering from drop down menu -Offering_network_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[4]" - -# Add Offering -Offering_add_xpath ="//div[3]/span" - -# Points to tbale that lists Offerings -Offering_table_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/table/tbody/tr/td/span" - -# Edit Button -Offering_edit_css = "a[alt=\"Edit\"] > span.icon" - -# Edit name box -Offering_editname_name = "name" - -# Edit description box -Offering_editdescription_name = "displaytext" - -# Edit finished click ok -Offering_editdone_css="div.button.done" - -# delete offering button for Disk only -Offering_delete_css = "a[alt=\"Delete Disk Offering\"] > span.icon" - -# delete offering button for Compute only -Offering_deletecompute_css = "a[alt=\"Delete Service Offering\"] > span.icon" - - - - -################################################################################################################################################################################################ - - -#### Templates Page - -# Selects Templates from drop down -template_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[1]" - -# Selects ISO from drop down -iso_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[2]" - -# Add Template -AddTemplate_xpath = "//div[3]/span" - -# Points to table where all templates are -template_table_xpath ="/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/table/tbody/tr/td/span" - -# Edit Template Button -template_edit_css = "a[alt=\"Edit\"] > span.icon" - -# Edit finished click OK -template_editdone_css = "div.button.done" - -# Delete Template button -template_delete_css = "a[alt=\"Delete Template\"] > span.icon" - - -################################################################################################################################################################################################ - - -## Login Page - -# Username box -login_username_css = "body.login > div.login > form > div.fields > div.field.username > input[name=\"username\"]" # Login>Username TextBox - -# Password Box -login_password_css = "body.login > div.login > form > div.fields > div.field.password > input[name=\"password\"]" # LoginPassword TextBox - -# Click ok to login -login_submit_css = "body.login > div.login > form > div.fields > input[type=\"submit\"]" # Login>Login Button (Submit button) - - -################################################################################################################################################################################################ - - -## Logout -logout_css = "div.cloudstack3-container > div.container > div.header > div.controls div.user > div.user-options > a[text=\"Logout\"]" # Logout -# logout_css = "div#header > div#user-options > a[href='#']" diff --git a/test/selenium/common/shared.py b/test/selenium/common/shared.py deleted file mode 100644 index 21c598ebbee8..000000000000 --- a/test/selenium/common/shared.py +++ /dev/null @@ -1,148 +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. - -#!/usr/bin/python -# coding: latin-1 - -from selenium.selenium import selenium -from selenium.common.exceptions import NoSuchElementException -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.common.exceptions import WebDriverException -from selenium.common.exceptions import TimeoutException -from selenium.webdriver.support import expected_conditions as EC # available since 2.26.0 -from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 -import re, sys, time, traceback - -def try_except_decor(func): - def try_except(*args, **kwargs): - try: - return func(*args, **kwargs) - except WebDriverException as err: - exc_type, exc_value, exc_traceback = sys.exc_info() - print "WebDriver error. Function: {0}, error: {1}".format(func.func_code, err) - print repr(traceback.format_exception(exc_type, exc_value,exc_traceback)) - except NoSuchElementException as err: - exc_type, exc_value, exc_traceback = sys.exc_info() - print "Element error. Function: {0}, error: {1}".format(func.func_code, err) - print repr(traceback.format_exception(exc_type, exc_value,exc_traceback)) - except TimeoutException as err: - exc_type, exc_value, exc_traceback = sys.exc_info() - print "Timeout error. Function: {0}, error: {1}".format(func.func_code, err) - print repr(traceback.format_exception(exc_type, exc_value,exc_traceback)) - - return try_except - -class Shared(object): - - @staticmethod - @try_except_decor - def option_selection(browser, element_type, element_name, option_text, wait_element_type = '', wait_element_name = ''): - - ret = False - Shared.wait_for_element(browser, element_type, element_name) - if element_type == 'id': - ele = browser.find_element_by_id(element_name) - elif element_type == 'class_name': - ele = browser.find_element_by_class_name(element_name) - options = ele.find_elements_by_tag_name('option') - option_names = [option.text for option in options] - if option_text not in option_names: - return ret - - for option in options: - if option.text.find(option_text) > -1: - option.click() - ret = True - time.sleep(1) - break - - if len(wait_element_type) > 0 and len(wait_element_name) > 0: - Shared.wait_for_element(browser, wait_element_type, wait_element_name) - return ret - - @staticmethod - @try_except_decor - def flash_message(browser): - try: - ele1 = browser.find_element_by_id('flashMessageArea') - except NoSuchElementException: - ele1 = None - if ele1 != None: - ele2 = ele1.find_element_by_class_name('flash_message') - if ele2 != None and ele2.text != None and len(ele2.text) > 0: - return ele2.text - else: - return '' - else: - return '' - - @staticmethod - @try_except_decor - def string_selection(browser, key, value, index = 0): - element = browser.find_elements_by_id(key)[index] - element.clear() - element.send_keys(value) - - @staticmethod - def wait_until_title_text(browser, text, waittime = 30): - wait = WebDriverWait(browser, waittime) - wait.until(lambda browser: browser.title.lower().find(text.lower()) > -1) - - @staticmethod - def wait_until_find_id(browser, element_id, waittime = 10): - wait = WebDriverWait(browser, waittime) - wait.until(lambda browser: browser.find_element_by_id(element_id)) - - @staticmethod - # the name should exist in the newer page, but not in older one - def wait_for_element(browser, element_type, name, waittime = 30): - wait = WebDriverWait(browser, waittime) - if element_type.lower() == 'id': - wait.until(EC.presence_of_element_located((By.ID, name))) - elif element_type.lower() == 'tag_name': - wait.until(EC.presence_of_element_located((By.TAG_NAME, name))) - elif element_type.lower() == 'class_name': - wait.until(EC.presence_of_element_located((By.CLASS_NAME, name))) - elif element_type.lower() == 'xpath': - wait.until(EC.presence_of_element_located((By.XPATH, name))) - elif element_type.lower() == 'link_text': - wait.until(EC.presence_of_element_located((By.LINK_TEXT, name))) - - #feed the string through directly - else: - wait.until(EC.presence_of_element_located(element_type, name)) - - time.sleep(1) - - def playing_around(self): - from threading import Timer - t = Timer(20,self.wait_for_invisible) - t.start() - - @staticmethod - #wait until something disappears - def wait_for_invisible(browser, element_type, name, waittime=30): - wait = WebDriverWait(browser, waittime) - - # the code base uses underscores, but the real string doesn't have em. - final_type = re.sub('_',' ',element_type) - - wait.until(EC.invisibility_of_element_located((final_type, name))) - - #this method isn't as slick as I hoped :( - time.sleep(1) diff --git a/test/selenium/cspages/accounts/accountspage.py b/test/selenium/cspages/accounts/accountspage.py deleted file mode 100644 index 15eccd3ffbc2..000000000000 --- a/test/selenium/cspages/accounts/accountspage.py +++ /dev/null @@ -1,175 +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. - -from selenium import webdriver -from selenium.common.exceptions import * -from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 -from selenium.webdriver.common.action_chains import ActionChains as action -from common import Global_Locators -from cspages.cspage import CloudStackPage - -from common.shared import * - -class AccountsPage(CloudStackPage): - - def __init__(self, browser): - self.browser = browser - self.accounts = [] - - @try_except_decor - def get_accounts(self): - rows = self.browser.find_elements_by_xpath("//div[@class='data-table']/table[@class='body']/tbody/tr") - for row in rows: - account = {} - columes = row.find_elements_by_tag_name('td') - account['Name'] = columes[0].get_attribute('title').lower() - account['Role'] = columes[1].get_attribute('title').lower() - account['Domain'] = columes[2].get_attribute('title').lower() - account['State'] = columes[3].get_attribute('title').lower() - self.accounts.append(account) - - @try_except_decor - def account_exists(self, name): - if len(self.accounts) == 0: - self.get_accounts() - account = [acct for acct in self.accounts if acct['Name'] == name.lower()] - if len(account) > 0: - return True - else: - return False - - @try_except_decor - def add_account(self, username = "", password = "", email = "", firstname = "", lastname = "", domain = "", account = "", type = "", timezone = "", network_domain = ""): - # type = role - if len(username) == 0 or len(password) == 0 or len(email) == 0 or len(firstname) == 0 or len(lastname) == 0 or len(domain) == 0 or len(type) == 0: - return; - if type not in ('User', 'Admin'): - print "Account type must be either User or Admin." - return; - if self.account_exists(username): - return - - # click Add Account - ele = self.browser.find_element_by_xpath("//div[@class='toolbar']") - ele1 = ele.find_element_by_xpath("//div[3]/span") - ele1.click() - - Shared.wait_for_element(self.browser, 'id', 'label_username') - ele = self.browser.find_element_by_xpath("(//input[@name='username' and @type='text' and @id='label_username'])") - ele.send_keys(username) - ele = self.browser.find_element_by_xpath("(//input[@name='password' and @type='password' and @id='password'])") - ele.send_keys(password) - ele = self.browser.find_element_by_xpath("(//input[@name='password-confirm' and @type='password' and @id='label_confirm_password'])") - ele.send_keys(password) - ele = self.browser.find_element_by_xpath("(//input[@name='email' and @type='text' and @id='label_email'])") - ele.send_keys(email) - ele = self.browser.find_element_by_xpath("(//input[@name='firstname' and @type='text' and @id='label_first_name'])") - ele.send_keys(firstname) - ele = self.browser.find_element_by_xpath("(//input[@name='lastname' and @type='text' and @id='label_last_name'])") - ele.send_keys(lastname) - Shared.option_selection(self.browser, 'id', 'label_domain', 'ROOT') - if len(account) > 0: - ele = self.browser.find_element_by_xpath("(//input[@name='account' and @type='text' and @id='label_account'])") - ele.send_keys(account) - Shared.option_selection(self.browser, 'id', 'label_type', type) - Shared.option_selection(self.browser, 'id', 'label_timezone', timezone) - if len(network_domain) > 0: - ele = self.browser.find_element_by_xpath("(//input[@name='networkdomain' and @type='text' and @id='label_network_domain'])") - ele.send_keys(network_domain) - self.button_add() - - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def select_account(self, username = "", domain = "", type = ""): - if len(username) == 0 or len(domain) == 0 or len(type) == 0: - return False; - if self.account_exists(username) == False: - print "The account does not exist" - return False - - # select the account - ele = self.browser.find_element_by_xpath("//div[@class='data-table']/div[@class='fixed-header']/table") - ele1 = ele.find_element_by_xpath("//tbody") - ele2 = ele1.find_elements_by_tag_name('tr') - for e in ele2: - ele3 = e.find_elements_by_tag_name('td') - # move mouse to quickview - if len(ele3) > 4 and \ - ele3[0].text == username and \ - ele3[1].text == type and \ - ele3[2].text == domain and \ - ele3[3].text == 'enabled': - ele3[4].find_element_by_tag_name('span').click() - Shared.wait_for_element(self.browser, 'class_name', 'details') - # select account - ele = self.browser.find_element_by_xpath("//div[@id='details-tab-details']/div[@class='details']/div/table/tbody/tr/td[@class='view-all']") - ele1 = ele.find_element_by_tag_name('a').find_element_by_tag_name('span').click() - break - - Shared.wait_for_element(self.browser, 'class_name', 'view-all') - - @try_except_decor - def delete_account(self, username = "", domain = "", type = ""): - if len(username) == 0 or len(domain) == 0 or len(type) == 0: - return False; - if self.account_exists(username) == False: - print "The account does not exist" - return False - - # find the account - ele = self.browser.find_element_by_xpath("//div[@class='data-table']/div[@class='fixed-header']/table") - ele1 = ele.find_element_by_xpath("//tbody") - ele2 = ele1.find_elements_by_tag_name('tr') - for e in ele2: - ele3 = e.find_elements_by_tag_name('td') - # move mouse to quickview - if len(ele3) > 4 and \ - ele3[0].text == username and \ - ele3[1].text == type and \ - ele3[2].text == domain and \ - ele3[3].text == 'enabled': - ele3[4].find_element_by_tag_name('span').click() - Shared.wait_for_element(self.browser, 'class_name', 'details') - # delete account - ele = self.browser.find_element_by_xpath("//div[@id='details-tab-details']/div[@class='details']/div/table/tbody/tr/td/div[@class='buttons']") - ele1 = ele.find_element_by_xpath("//div[@class='action remove single text' and @title='Delete account']/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'ui-dialog-buttonset') - self.button_yes() - break - - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_cancel(self): - ele = self.browser.find_element_by_xpath("/html/body/div[4]/div[2]/div/button[1]/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_add(self): - ele = self.browser.find_element_by_xpath("/html/body/div[4]/div[2]/div/button[2]/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_no(self): - ele = self.browser.find_element_by_xpath("/html/body/div[4]/div[10]/div/button[1]/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_yes(self): - ele = self.browser.find_element_by_xpath("/html/body/div[4]/div[10]/div/button[2]/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') diff --git a/test/selenium/cspages/accounts/userspage.py b/test/selenium/cspages/accounts/userspage.py deleted file mode 100644 index 25375f5d9add..000000000000 --- a/test/selenium/cspages/accounts/userspage.py +++ /dev/null @@ -1,146 +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. - -from selenium import webdriver -from selenium.common.exceptions import * -from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 -from selenium.webdriver.common.action_chains import ActionChains as action -from common import Global_Locators -from cspages.cspage import CloudStackPage - -from common.shared import * - -class UsersPage(CloudStackPage): - - def __init__(self, browser): - self.browser = browser - self.users = [] - - @try_except_decor - def get_users(self): - ele = self.browser.find_element_by_xpath("//div[@class='container cloudStack-widget cloudBrowser']") - rows = ele.find_elements_by_xpath("//div[@class='panel']/div[2]/div[@class='view list-view']/div[@class='data-table']/table[@class='body']/tbody/tr") - for row in rows: - user = {} - columes = row.find_elements_by_tag_name('td') - user['username'] = columes[0].get_attribute('title').lower() - user['firstname'] = columes[1].get_attribute('title').lower() - user['lastname'] = columes[2].get_attribute('title').lower() - self.users.append(user) - - @try_except_decor - def user_exists(self, username): - if len(self.users) == 0: - self.get_users() - users = [u for u in self.users if u['username'] == username.lower()] - if len(users) > 0: - return True - else: - return False - - @try_except_decor - def add_user(self, username = "", password = "", email = "", firstname = "", lastname = "", timezone = ""): - if len(username) == 0 or len(password) == 0 or len(email) == 0 or len(firstname) == 0 or len(lastname) == 0: - return; - if self.user_exists(username): - return - - # click Add User - ele = self.browser.find_element_by_xpath("//div[@class='container cloudStack-widget cloudBrowser']") - ele1 = ele.find_element_by_xpath("//div[@class='panel']/div[2]/div[@class='view list-view']/div[@class='toolbar']/div[@class='button action add reduced-hide']/span") - ele1.click() - - Shared.wait_for_element(self.browser, 'id', 'label_username') - ele = self.browser.find_element_by_xpath("(//input[@name='username' and @type='text' and @id='label_username'])") - ele.send_keys(username) - ele = self.browser.find_element_by_xpath("(//input[@name='password' and @type='password' and @id='password'])") - ele.send_keys(password) - ele = self.browser.find_element_by_xpath("(//input[@name='password-confirm' and @type='password' and @id='label_confirm_password'])") - ele.send_keys(password) - ele = self.browser.find_element_by_xpath("(//input[@name='email' and @type='text' and @id='label_email'])") - ele.send_keys(email) - ele = self.browser.find_element_by_xpath("(//input[@name='firstname' and @type='text' and @id='label_first_name'])") - ele.send_keys(firstname) - ele = self.browser.find_element_by_xpath("(//input[@name='lastname' and @type='text' and @id='label_last_name'])") - ele.send_keys(lastname) - Shared.option_selection(self.browser, 'id', 'label_timezone', timezone) - self.button_ok() - - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def delete_user(self, username = "", firstname = "", lastname = ""): - if len(username) == 0 or len(firstname) == 0 or len(lastname) == 0: - return False; - if self.user_exists(username) == False: - print "The user does not exist" - return False - - # find the user - ele = self.browser.find_element_by_xpath("//div[@class='container cloudStack-widget cloudBrowser']") - ele1 = ele.find_element_by_xpath("//div[@class='panel']/div[2]/div[@class='view list-view']/div[@class='data-table']/table[@class='body']/tbody") - ele2 = ele1.find_elements_by_tag_name('tr') - for e in ele2: - ele3 = e.find_elements_by_tag_name('td') - # move mouse to quickview - if len(ele3) > 3 and \ - ele3[0].text == username and \ - ele3[1].text == firstname and \ - ele3[2].text == lastname: - ele3[3].find_element_by_tag_name('span').click() - Shared.wait_for_element(self.browser, 'class_name', 'details') - # delete user - ele = self.browser.find_element_by_xpath("//div[@id='details-tab-details']/div[@class='details']/div/table/tbody/tr/td/div[@class='buttons']") - ele1 = ele.find_element_by_xpath("//div[@class='action remove single text' and @title='Delete User']/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'ui-dialog-buttonset') - self.button_yes() - break - - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_yes(self): - eles = self.browser.find_elements_by_xpath("//div[@class='ui-dialog-buttonset']/button[@type='button' and @role='button']") - for e in eles: - ele = e.find_element_by_class_name('ui-button-text') - if e.text == 'Yes': - e.click() - break - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_no(self): - ele = self.browser.find_element_by_xpath("/html/body/div[4]/div[10]/div/button[1]/span").click() - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_ok(self): - eles = self.browser.find_elements_by_xpath("//button[@type='button' and @role='button']") - for e in eles: - if e.text == 'OK': - e.click() - break - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') - - @try_except_decor - def button_cancel(self): - eles = self.browser.find_elements_by_xpath("//button[@type='button' and @role='button']") - for e in eles: - if e.text == 'Cancel': - e.click() - break - Shared.wait_for_element(self.browser, 'class_name', 'fixed-header') diff --git a/test/selenium/cspages/dashboard/dashboardpage.py b/test/selenium/cspages/dashboard/dashboardpage.py deleted file mode 100644 index 12a38a1978b8..000000000000 --- a/test/selenium/cspages/dashboard/dashboardpage.py +++ /dev/null @@ -1,73 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -from selenium import webdriver -from selenium.common.exceptions import * -from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 -from selenium.webdriver.common.action_chains import ActionChains as action -from common import Global_Locators -from cspages.cspage import CloudStackPage - -from common.shared import * - -class DashboardPage(CloudStackPage): - - def __init__(self, browser): - self.browser = browser - self.active_item = "" - self.items = [] - - @try_except_decor - def get_active_item(self): - self.active_item = "" - lis = self.browser.find_elements_by_xpath("//*[@id='navigation']/ul/li") - for li in lis: - if li.get_attribute('class').find('active') > 0: - self.active_item = li.get_attribute('class')[:(li.get_attribute('class').index(' active'))] - return self.active_item - - @try_except_decor - def get_items(self): - lis = self.browser.find_elements_by_xpath("//*[@id='navigation']/ul/li") - for li in lis: - item = li.get_attribute('class')[len('navigation-item '):] - if item.find('active') > 0: - item = item[:(item.index(' active'))] - if item.find('first') > 0: - item = item[:(item.index(' first'))] - if item.find('last') > 0: - item = item[:(item.index(' last'))] - self.items.append(item.lower()) - return self.items -# import pdb -# pdb.set_trace() - - @try_except_decor - def navigate_to(self, item_name): - if len(self.items) == 0: - self.get_items() - if item_name is None or len(item_name) == 0 or \ - item_name.lower() not in self.items or \ - (len(self.active_item) > 0 and self.active_item.lower().find(item_name.lower()) > 0): - return - - lis = self.browser.find_elements_by_xpath("//*[@id='navigation']/ul/li") - for li in lis: - if li.get_attribute('class').lower().find(item_name.lower()) > 0: - li.click() - time.sleep(3) - return diff --git a/test/selenium/cspages/login/loginpage.py b/test/selenium/cspages/login/loginpage.py deleted file mode 100644 index f4b0e8adba2d..000000000000 --- a/test/selenium/cspages/login/loginpage.py +++ /dev/null @@ -1,103 +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. - -from selenium import webdriver -from selenium.common.exceptions import * -from selenium.webdriver.support.ui import WebDriverWait # available since 2.4.0 -from selenium.webdriver.common.action_chains import ActionChains as action -from common import Global_Locators -from cspages.cspage import CloudStackPage - -from common.shared import * - -import pdb - -class LoginPage(CloudStackPage): - def __init__(self, browser): - self.browser = browser - self.username = "" - self.password = "" - self.language = "" - - @try_except_decor - def set_username(self, username): - self.username = username - usernameElement = self.browser.find_element_by_css_selector(Global_Locators.login_username_css) - usernameElement.send_keys(self.username) - - @try_except_decor - def set_password(self, password): - self.password = password - passwordElement = self.browser.find_element_by_css_selector(Global_Locators.login_password_css) - passwordElement.send_keys(self.password) - self.pwelement = passwordElement - - @try_except_decor - def set_language(self, language): - self.language = language - options = self.browser.find_elements_by_xpath('/html/body/div[3]/form/div[2]/div[4]/select/option') - for option in options: - if len(option.get_attribute('text')) > 0 and option.get_attribute('text').lower() == language.lower(): - option.click() - break - - @try_except_decor - def login(self, expect_fail = False): - if self.username == "" or self.password == "": - print "Must set email and password before logging in" - return - loginElement = self.browser.find_element_by_css_selector(Global_Locators.login_submit_css) - loginElement.click() - - time.sleep(3) - try: - # in case we have that "Hello and Welcome to CloudStack" page - ele = None - ele = self.browser.find_element_by_xpath("//input[@type='submit' and @class='button goTo advanced-installation' and @value='I have used CloudStack before, skip this guide']") - if ele is not None: - ele.click() - time.sleep(2) - except NoSuchElementException as err: - pass - - @try_except_decor - def logout(self, directly_logout = False): - - Shared.wait_for_element(self.browser, 'id', 'user') - - # must click this icon options first - if directly_logout == False: - try: - ele = self.browser.find_element_by_xpath("//div[@id='user-options' and @style='display: block;']") - if ele is None: - ele1 = self.browser.find_element_by_xpath("//div[@id='user' and @class='button']/div[@class='icon options']/div[@class='icon arrow']").click() - except NoSuchElementException as err: - ele1 = self.browser.find_element_by_xpath("//div[@id='user' and @class='button']/div[@class='icon options']/div[@class='icon arrow']").click() - time.sleep(1) - - ele2 = self.browser.find_element_by_xpath("//div[@id='user' and @class='button']/div[@id='user-options']/a[1]").click() - - Shared.wait_for_element(self.browser, 'class_name', 'login') - - @try_except_decor - def get_error_msg(self, loginpage_url): - if loginpage_url is not None and len(loginpage_url) > 0 and \ - (self.browser.current_url.find(loginpage_url) > -1 or loginpage_url.find(self.browser.current_url) > -1): - ele = self.browser.find_element_by_id('std-err') - return ele.text - else: - return "" diff --git a/test/selenium/cstests/__init__.py b/test/selenium/cstests/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/test/selenium/cstests/__init__.py +++ /dev/null @@ -1,16 +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. diff --git a/test/selenium/cstests/regressiontests/__init__.py b/test/selenium/cstests/regressiontests/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/test/selenium/cstests/regressiontests/__init__.py +++ /dev/null @@ -1,16 +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. diff --git a/test/selenium/cstests/smoketests/__init__.py b/test/selenium/cstests/smoketests/__init__.py deleted file mode 100644 index 13a83393a912..000000000000 --- a/test/selenium/cstests/smoketests/__init__.py +++ /dev/null @@ -1,16 +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. diff --git a/test/selenium/cstests/smoketests/adduser_test.py b/test/selenium/cstests/smoketests/adduser_test.py deleted file mode 100644 index b19a06011586..000000000000 --- a/test/selenium/cstests/smoketests/adduser_test.py +++ /dev/null @@ -1,103 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg -import cspages.login.loginpage as loginpage -import cspages.dashboard.dashboardpage as dashboardpage -import cspages.accounts.accountspage as accountspage -import cspages.accounts.userspage as userspage - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSAddUser(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - - shared.Shared.wait_for_element(self.browser.browser, 'id', 'navigation') - - time.sleep(3) - - self.dashboardpage = dashboardpage.DashboardPage(self.browser.get_browser()) - - # navigate to Accounts page - self.dashboardpage.navigate_to('accounts') - - # make sure we are on Accounts page - activeitem = self.dashboardpage.get_active_item() - if activeitem.find('accounts') < 0: - self.assertRaises(ValueError, self.dashboardpage.get_active_item(), activeitem) - - # now we are at Accounts page - self.accountspage = accountspage.AccountsPage(self.browser.get_browser()) - self.accountspage.select_account(username = smokecfg['account']['username'], - domain = smokecfg['account']['domain'], - type = smokecfg['account']['type'], - ) - - # now we are at users page - self.userspage = userspage.UsersPage(self.browser.get_browser()) - self.userspage.add_user(username = smokecfg['new user']['username'], - password = smokecfg['new user']['password'], - email = smokecfg['new user']['email'], - firstname = smokecfg['new user']['firstname'], - lastname = smokecfg['new user']['lastname'], - timezone = smokecfg['new user']['timezone'], - ) - - self.loginpage.logout() - - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login') - - def xtest_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/adduseraccount_test.py b/test/selenium/cstests/smoketests/adduseraccount_test.py deleted file mode 100644 index e687c6b53a5b..000000000000 --- a/test/selenium/cstests/smoketests/adduseraccount_test.py +++ /dev/null @@ -1,96 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg -import cspages.login.loginpage as loginpage -import cspages.dashboard.dashboardpage as dashboardpage -import cspages.accounts.accountspage as accountspage - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSAddUserAccount(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - - shared.Shared.wait_for_element(self.browser.browser, 'id', 'navigation') - - time.sleep(3) - - self.dashboardpage = dashboardpage.DashboardPage(self.browser.get_browser()) - - # navigate to Accounts page - self.dashboardpage.navigate_to('accounts') - - # make sure we are on Accounts page - activeitem = self.dashboardpage.get_active_item() - if activeitem.find('accounts') < 0: - self.assertRaises(ValueError, self.dashboardpage.get_active_item(), activeitem) - - # now we are at Accounts page - self.accountspage = accountspage.AccountsPage(self.browser.get_browser()) - self.accountspage.add_account(username = smokecfg['new user account']['username'], - password = smokecfg['new user account']['password'], - email = smokecfg['new user account']['email'], - firstname = smokecfg['new user account']['firstname'], - lastname = smokecfg['new user account']['lastname'], - domain = smokecfg['new user account']['domain'], - type = smokecfg['new user account']['type'], - timezone = smokecfg['new user account']['timezone'], - ) - self.loginpage.logout() - - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login') - - def xtest_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/deleteuser_test.py b/test/selenium/cstests/smoketests/deleteuser_test.py deleted file mode 100644 index 8d3d28d68eee..000000000000 --- a/test/selenium/cstests/smoketests/deleteuser_test.py +++ /dev/null @@ -1,100 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg -import cspages.login.loginpage as loginpage -import cspages.dashboard.dashboardpage as dashboardpage -import cspages.accounts.accountspage as accountspage -import cspages.accounts.userspage as userspage - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSDeleteUser(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - - shared.Shared.wait_for_element(self.browser.browser, 'id', 'navigation') - - time.sleep(3) - - self.dashboardpage = dashboardpage.DashboardPage(self.browser.get_browser()) - - # navigate to Accounts page - self.dashboardpage.navigate_to('accounts') - - # make sure we are on Accounts page - activeitem = self.dashboardpage.get_active_item() - if activeitem.find('accounts') < 0: - self.assertRaises(ValueError, self.dashboardpage.get_active_item(), activeitem) - - # now we are at Accounts page - self.accountspage = accountspage.AccountsPage(self.browser.get_browser()) - self.accountspage.select_account(username = smokecfg['account']['username'], - domain = smokecfg['account']['domain'], - type = smokecfg['account']['type'], - ) - - # now we are at users page - self.userspage = userspage.UsersPage(self.browser.get_browser()) - self.userspage.delete_user(username = smokecfg['new user']['username'], - firstname = smokecfg['new user']['firstname'], - lastname = smokecfg['new user']['lastname'], - ) - - self.loginpage.logout() - - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login') - - def xtest_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/deleteuseraccount_test.py b/test/selenium/cstests/smoketests/deleteuseraccount_test.py deleted file mode 100644 index 179646adeea5..000000000000 --- a/test/selenium/cstests/smoketests/deleteuseraccount_test.py +++ /dev/null @@ -1,91 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg -import cspages.login.loginpage as loginpage -import cspages.dashboard.dashboardpage as dashboardpage -import cspages.accounts.accountspage as accountspage - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSDeleteAccount(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - - shared.Shared.wait_for_element(self.browser.browser, 'id', 'navigation') - - time.sleep(3) - - self.dashboardpage = dashboardpage.DashboardPage(self.browser.get_browser()) - - # navigate to Accounts page - self.dashboardpage.navigate_to('accounts') - - # make sure we are on Accounts page - activeitem = self.dashboardpage.get_active_item() - if activeitem.find('accounts') < 0: - self.assertRaises(ValueError, self.dashboardpage.get_active_item(), activeitem) - - # now we are at Accounts page - self.accountspage = accountspage.AccountsPage(self.browser.get_browser()) - self.accountspage.delete_account(username = smokecfg['new user account']['username'], - domain = smokecfg['new user account']['domain'], - type = smokecfg['new user account']['type'], - ) - self.loginpage.logout() - - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login') - - def xtest_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/global_settings_test.py b/test/selenium/cstests/smoketests/global_settings_test.py deleted file mode 100644 index 2fc0ff194d05..000000000000 --- a/test/selenium/cstests/smoketests/global_settings_test.py +++ /dev/null @@ -1,69 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg -import cspages.login.loginpage as loginpage -import cspages.dashboard.dashboardpage as dashboardpage - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSGlobalSettings(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - shared.Shared.wait_for_element(self.browser.browser, 'id', 'navigation') - - time.sleep(3) - - self.dashboardpage = dashboardpage.DashboardPage(self.browser.get_browser()) - active_item = self.dashboardpage.get_active_item() - print "active item: ", active_item - - self.loginpage.logout() - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login') - - def xtest_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/login_logout_as_JohnD_test.py b/test/selenium/cstests/smoketests/login_logout_as_JohnD_test.py deleted file mode 100644 index a01f2f8fbcbf..000000000000 --- a/test/selenium/cstests/smoketests/login_logout_as_JohnD_test.py +++ /dev/null @@ -1,61 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import cspages.login.loginpage as loginpage -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSLoginLogout(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['new user account']['username']) - self.loginpage.set_password(smokecfg['new user account']['password']) - self.loginpage.login() - - time.sleep(5) - - self.loginpage.logout(directly_logout = True) - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login', waittime = 300) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/login_logout_test.py b/test/selenium/cstests/smoketests/login_logout_test.py deleted file mode 100644 index c94e4e322945..000000000000 --- a/test/selenium/cstests/smoketests/login_logout_test.py +++ /dev/null @@ -1,190 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import cspages.login.loginpage as loginpage -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSLoginLogout(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - shared.Shared.wait_for_element(self.browser.get_browser(), 'id', 'navigation', waittime = 300) - - time.sleep(5) - - self.loginpage.logout(directly_logout = True) - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login', waittime = 300) - - def test_failure_1(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['badusername']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - - def test_failure_2(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['badpassword']) - self.loginpage.login(expect_fail = True) - - def test_failure_3(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['badusername']) - self.loginpage.set_password(smokecfg['badpassword']) - self.loginpage.login(expect_fail = True) - - def test_failure_4(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['sqlinjection_1']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - - def test_failure_5(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['sqlinjection_2']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - - def test_failure_6(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['sqlinjection_3']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - - def test_failure_7(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['sqlinjection_4']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - - def test_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/navigation_test.py b/test/selenium/cstests/smoketests/navigation_test.py deleted file mode 100644 index 01ea449505ec..000000000000 --- a/test/selenium/cstests/smoketests/navigation_test.py +++ /dev/null @@ -1,79 +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. - -import unittest -import sys, os, time -import json - -sys.path.append('./') - -import browser.firefox as firefox -import common.shared as shared -from cstests.smoketests.smokecfg import smokecfg -import cspages.login.loginpage as loginpage -import cspages.dashboard.dashboardpage as dashboardpage - -# from cstests.smoketests import smokecfg as smokecfg - -class TestCSnavigation(unittest.TestCase): - def setUp(self): - # Create a new instance of the Firefox browser - self.browser = firefox.Firefox('firefox') - - def tearDown(self): - self.browser.quit_browser() - - def test_success(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - - # wait for at most 5 minutes, in case we have an anoyingly slow server - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'select-language', waittime = 300) - - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - - shared.Shared.wait_for_element(self.browser.get_browser(), 'class_name', 'fields', waittime = 300) - - self.loginpage.set_username(smokecfg['username']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login() - - shared.Shared.wait_for_element(self.browser.browser, 'id', 'navigation') - - time.sleep(3) - - self.dashboardpage = dashboardpage.DashboardPage(self.browser.get_browser()) - - items = self.dashboardpage.get_items() - [self.dashboardpage.navigate_to(item) for item in items] - - self.loginpage.logout() - - shared.Shared.wait_for_element(self.browser.browser, 'class_name', 'login') - - def xtest_failure_8(self): - self.browser.set_url(smokecfg['cssite']) - self.loginpage = loginpage.LoginPage(self.browser.get_browser()) - # language selection must be done before username and password - self.loginpage.set_language(smokecfg['language']) - self.loginpage.set_username(smokecfg['sqlinjection_5']) - self.loginpage.set_password(smokecfg['password']) - self.loginpage.login(expect_fail = True) - -if __name__ == '__main__': - unittest.main() diff --git a/test/selenium/cstests/smoketests/smokecfg.py b/test/selenium/cstests/smoketests/smokecfg.py deleted file mode 100644 index b6c09a660d9e..000000000000 --- a/test/selenium/cstests/smoketests/smokecfg.py +++ /dev/null @@ -1,62 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -#!/usr/bin/python -# coding: latin-1 - -smokecfg = { - 'browser': 'Firefox', -# 'window position': '10, 10', # upper left coordinates -# 'window size': '2000, 1500', - 'cssite': 'http://127.0.0.1:8080/client/', -# 'cssite': 'http://192.168.1.31:8080/client/', - 'username': 'admin', - 'password': 'password', - 'badusername': 'badname', - 'badpassword': 'badpassword', - 'sqlinjection_1': '\' or 1=1 --\'', - 'sqlinjection_2': '\' union select 1, badusername, badpassword, 1--\'', - 'sqlinjection_3': '\' union select @@version,1,1,1--\'', - 'sqlinjection_4': '\'; drop table user--\'', - 'sqlinjection_5': '\'OR\' \'=\'', - 'language': 'English', - - # add a new user account - 'new user account':{'username': 'JohnD', - 'password': 'password', - 'email': 'johndoe@aol.com', - 'firstname': 'John', - 'lastname': 'Doe', - 'domain': 'ROOT', - 'type': 'User', # either 'User' or 'Admin' - 'timezone': 'US/Eastern [Eastern Standard Time]', - }, - # add a new user under JohnD - 'account': {'username': 'JohnD', - 'domain': 'ROOT', - 'type': 'User', - }, - # add a new user - 'new user': {'username': 'JaneD', - 'password': 'password', - 'email': 'janedoe@aol.com', - 'firstname': 'Jane', - 'lastname': 'Doe', - 'timezone': 'US/Eastern [Eastern Standard Time]', - }, - - } diff --git a/test/selenium/lib/Global_Locators.py b/test/selenium/lib/Global_Locators.py deleted file mode 100644 index 111e1549353d..000000000000 --- a/test/selenium/lib/Global_Locators.py +++ /dev/null @@ -1,222 +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. - -''' -Variable Names are as follows -Logical Page Descriptor_____What Element Represents and/or where it is_____LocatorType - - -For Example :: - -instances_xpath = "//div[@id='navigation']/ul/li[2]/span[2]" - -Means this is:: xpath link for Instances which is present on Dashboard. -Any test cases that requires to go into Instances from Dashboard can use this variable now. - -This may not be intuitive as you go deep into the tree. - - - -for example - -stopinstanceforce_id - -The best way to know what this represents is to track by variable name -Under Instances / any instance is click on any instance (applies to any instance) / stop instance has a force stop check box when you click. -This link represents that. - - -Steps below do not have global locators. - -PF rule steps including and after filling port numbers. (Refer to vmLifeAndNetwork.py / def test_PF) -FW rule steps including and after filling port numbers. (Refer to vmLifeAndNetwork.py / def test_PF) -ADD Disk Offering page has Names, description, storage type etc etc -ADD Compute Offering page has Names, description, CPU Cores, CPU clocks type etc etc - -Create Acc, Delete Acc, Login and Logout are for test flow and are not test cases. They do not have global Locators. - -Such and many more data entry points that appear only once and hence we do not need glonal names for them. They are hard coded as and when needed in the scripts. - - -''' - -################################################################################################################################################################################################ - -## Links on the Main UI page (Dash board). Listed in the order they appear on screen -dashboard_xpath = "//div[@id='navigation']/ul/li" -instances_xpath = "//div[@id='navigation']/ul/li[2]/span[2]" # Link for Instance and following as self explanatory -storage_xpath = "//div[@id='navigation']/ul/li[3]/span[2]" -network_xpath = "//div[@id='navigation']/ul/li[4]/span[2]" -templates_xpath = "//div[@id='navigation']/ul/li[5]/span[2]" -events_xpath = "//div[@id='navigation']/ul/li[6]/span[2]" -projects_xpath = "//div[@id='navigation']/ul/li[7]/span[2]" -accounts_xpath = "//div[@id='navigation']/ul/li[8]/span[2]" -domains_xpath = "//div[@id='navigation']/ul/li[9]/span[2]" -infrastructure_xpath = "//div[@id='navigation']/ul/li[10]/span[2]" -globalSettings_xpath = "//div[@id='navigation']/ul/li[11]/span[2]" -serviceOfferings_xpath = "//div[@id='navigation']/ul/li[12]/span[2]" - -################################################################################################################################################################################################ - -## Instances Page -## Instances Main page - - -# Add Instance Button on top right corner of Instances page -add_instance_xpath = "//div[2]/div/div[2]/div/div[2]/span" - -# Add Instance Wizard next button -add_instance_next_xpath = "//div[4]/div[2]/div[3]/div[3]/span" - -# Table that lists all VM's under Instances page; General usage is to traverse through this table and search for the VM we are interested in. -instances_table_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/table/tbody/tr/td/span" - - -# Click any instance and following are available - -# Click ok on confirmation pop-up box for most actions listed below -actionconfirm_xpath = ("//button[@type='button']") - -# status of VM running. Click on VM > 3rd row in table -state_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div[2]/div[2]/div/div/div[2]/div/table/tbody/tr[3]/td[2]/span" - -# Stop instance icon -stopinstance_css = "a[alt=\"Stop Instance\"] > span.icon" - -# stop instance forcefully check box available after stop instance is executed in separate pop up -stopinstanceforce_id = ("force_stop") - -# start instance icon -startinstance_css = "a[alt=\"Start Instance\"] > span.icon" - -yesconfirmation_xapth = "(//button[@type='button'])[2]" - - -# Destroy instance icon -destroyinstance_css = "a[alt=\"Destroy Instance\"] > span.icon" - -#Restore Instance icon -restoreinstance_css = "a[alt=\"Restore Instance\"] > span.icon" - -# Reboot instance -rebootinstance_css = "a[alt=\"Reboot Instance\"] > span.icon" - -################################################################################################################################################################################################ - - -## Network Page - -# Table that lists all Networks under Network page; General usage is to traverse through this table and search for the network we are interested in. -network_networktable_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div[3]/div[2]/div/div[2]/table/tbody/tr/td/span" - -# View IP addresses button on each network page -viewIp_css="div.view-all > a > span" - -# Acquire a new ip -acquireIP_xpath="//div[2]/div/div/div[2]/span" -# List of IP's within a netork table -network_iptables_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div[3]/div[2]/div/div[2]/table/tbody/tr/td/span" -# Configuration tab for each IP -ipConfiguration_text="Configuration" -# PF under configuration for each IP -ip_PF = "li.portForwarding > div.view-details" - - -################################################################################################################################################################################################ - - -## Servivce Offering Page - -# Selects Compute offering from drop down menu -Offering_compute_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[1]" - -# Selects System offering from drop down menu -Offering_system_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[2]" - -# Selects Disk offering from drop down menu -Offering_disk_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[3]" - -# Selects Network offering from drop down menu -Offering_network_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[4]" - -# Add Offering -Offering_add_xpath ="//div[3]/span" - -# Points to tbale that lists Offerings -Offering_table_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/table/tbody/tr/td/span" - -# Edit Button -Offering_edit_css = "a[alt=\"Edit\"] > span.icon" - -# Edit name box -Offering_editname_name = "name" - -# Edit description box -Offering_editdescription_name = "displaytext" - -# Edit finished click ok -Offering_editdone_css="div.button.done" - -# delete offering button for Disk only -Offering_delete_css = "a[alt=\"Delete Disk Offering\"] > span.icon" - -# delete offering button for Compute only -Offering_deletecompute_css = "a[alt=\"Delete Service Offering\"] > span.icon" - - - - -################################################################################################################################################################################################ - - -#### Templates Page - -# Selects Templates from drop down -template_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[1]" - -# Selects ISO from drop down -iso_xpath = "/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div/div/div/select/option[2]" - -# Add Template -AddTemplate_xpath = "//div[3]/span" - -# Points to table where all templates are -template_table_xpath ="/html/body/div/div/div[2]/div[2]/div[2]/div/div[2]/div[2]/table/tbody/tr/td/span" - -# Edit Template Button -template_edit_css = "a[alt=\"Edit\"] > span.icon" - -# Edit finished click OK -template_editdone_css = "div.button.done" - -# Delete Template button -template_delete_css = "a[alt=\"Delete Template\"] > span.icon" - - -################################################################################################################################################################################################ - - -## Login Page - -# Username box -login_username_css = "body.login > div.login > form > div.fields > div.field.username > input[name=\"username\"]" # Login>Username TextBox - -# Password Box -login_password_css = "body.login > div.login > form > div.fields > div.field.password > input[name=\"password\"]" # LoginPassword TextBox - -# Click ok to login -login_submit_css = "body.login > div.login > form > div.fields > input[type=\"submit\"]" # Login>Login Button (Submit button) diff --git a/test/selenium/smoke/Login_and_Accounts.py b/test/selenium/smoke/Login_and_Accounts.py deleted file mode 100644 index 2b3aee420e83..000000000000 --- a/test/selenium/smoke/Login_and_Accounts.py +++ /dev/null @@ -1,254 +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. - -import sys, os -sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/'+'../lib')) - - -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import Select -from selenium.common.exceptions import NoSuchElementException -import unittest, time -import Global_Locators -import initialize - - - -class login(unittest.TestCase): - - - def setUp(self): - - MS_URL = initialize.getMSip() - self.driver = initialize.getOrCreateWebdriver() - self.base_url = "http://"+ MS_URL +":8080/" # Your management Server IP goes here - self.verificationErrors = [] - - - def test_login(self): - - # Here we will clear the test box for Username and Password and fill them with actual login data. - # After that we will click Login (Submit button) - driver = self.driver - driver.maximize_window() - driver.get(self.base_url + "client/") - driver.find_element_by_css_selector(Global_Locators.login_username_css).clear() - driver.find_element_by_css_selector(Global_Locators.login_username_css).send_keys("admin") - driver.find_element_by_css_selector(Global_Locators.login_password_css).clear() - driver.find_element_by_css_selector(Global_Locators.login_password_css).send_keys("password") - driver.find_element_by_css_selector(Global_Locators.login_submit_css).click() - time.sleep(5) - - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - -################################################################################################################################################ - - - -class logout(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.driver.implicitly_wait(100) - self.verificationErrors = [] - - - - def test_logout(self): - - # Here we will clear the test box for Username and Password and fill them with actual login data. - # After that we will click Login (Submit button) - driver = self.driver - driver.find_element_by_xpath("//div[@id='navigation']/ul/li").click() - driver.find_element_by_css_selector("div.icon.options").click() - driver.find_element_by_link_text("Logout").click() - - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - -################################################################################################################################################ - - - -class login_test(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_logintest(self): - - # Here we will clear the test box for Username and Password and fill them with actual login data. - # After that we will click Login (Submit button) - driver = self.driver - driver.find_element_by_css_selector(Global_Locators.login_username_css).clear() - driver.find_element_by_css_selector(Global_Locators.login_username_css).send_keys("test") - driver.find_element_by_css_selector(Global_Locators.login_password_css).clear() - driver.find_element_by_css_selector(Global_Locators.login_password_css).send_keys("password") - driver.find_element_by_css_selector(Global_Locators.login_submit_css).click() - time.sleep(5) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - -################################################################################################################################################ - - -class createAcc(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_createacc(self): - - driver = self.driver - self.driver.implicitly_wait(100) - driver.find_element_by_xpath("//div[@id='navigation']/ul/li[8]/span[2]").click() - driver.find_element_by_xpath("//div[3]/span").click() - driver.find_element_by_id("label_username").clear() - driver.find_element_by_id("label_username").send_keys("test") - driver.find_element_by_id("password").clear() - driver.find_element_by_id("password").send_keys("password") - driver.find_element_by_id("label_confirm_password").clear() - driver.find_element_by_id("label_confirm_password").send_keys("password") - driver.find_element_by_id("label_email").clear() - driver.find_element_by_id("label_email").send_keys("test@citrix.com") - driver.find_element_by_id("label_first_name").clear() - driver.find_element_by_id("label_first_name").send_keys("test") - driver.find_element_by_id("label_last_name").clear() - driver.find_element_by_id("label_last_name").send_keys("test") - driver.find_element_by_id("label_domain").click() - Select(driver.find_element_by_id("label_type")).select_by_visible_text("Admin") - Select(driver.find_element_by_id("label_timezone")).select_by_visible_text("[UTC-08:00] Pacific Standard Time") - driver.find_element_by_xpath("//button[@type='button']").click() - - # Go to Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(30) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - -################################################################################################################################################ - - -class tearAcc(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_tearacc(self): - - driver = self.driver - driver.find_element_by_css_selector("li.navigation-item.accounts").click() - driver.find_element_by_css_selector("tr.odd > td.name.first").click() - driver.find_element_by_css_selector("a[alt=\"Delete account\"] > span.icon").click() - driver.find_element_by_xpath("(//button[@type='button'])[2]").click() - - # Go to Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(30) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - def tearDown(self): - - self.driver.quit() - self.assertEqual([], self.verificationErrors) - - - -################################################################################################################################################ diff --git a/test/selenium/smoke/Service_Offering.py b/test/selenium/smoke/Service_Offering.py deleted file mode 100644 index 5c8a4475ba59..000000000000 --- a/test/selenium/smoke/Service_Offering.py +++ /dev/null @@ -1,426 +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. - -import sys, os -sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/'+'../lib')) - -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import Select -from selenium.common.exceptions import NoSuchElementException -import unittest, time -import initialize -import Global_Locators - - - - -class Disk_offering_Add(unittest.TestCase): - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_diskadd(self): - - driver = self.driver - self.driver.implicitly_wait(200) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Service Offerings - driver.find_element_by_xpath(Global_Locators.serviceOfferings_xpath).click() - - #Select Disk offering - driver.find_element_by_xpath(Global_Locators.Offering_disk_xpath).click() - - # Add offering - driver.find_element_by_xpath(Global_Locators.Offering_add_xpath).click() - - # Following have names.. so they do not have their global entries. - driver.find_element_by_name("name").clear() - driver.find_element_by_name("name").send_keys("Test Disk Name") - driver.find_element_by_name("description").clear() - driver.find_element_by_name("description").send_keys("Test Disk Description") - driver.find_element_by_name("disksize").clear() - driver.find_element_by_name("disksize").send_keys("1") - driver.find_element_by_xpath("//button[@type='button']").click() - time.sleep(20) - - ##Verification will be if this offering shows up into table and we can actually edit it. - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - def tearDown(self): - self.assertEqual([], self.verificationErrors) - - - - - -class Disk_offering_Edit(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_diskedit(self): - - driver = self.driver - self.driver.implicitly_wait(200) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Service Offerings - driver.find_element_by_xpath(Global_Locators.serviceOfferings_xpath).click() - - #Select Disk offering - driver.find_element_by_xpath(Global_Locators.Offering_disk_xpath).click() - - # We will be searching for our disk offering into the table - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.Offering_table_xpath) # This returns a list of all Offerings in table - - for link in linkclass: - - if link.text == "Test Disk Name": - link.click() - - time.sleep(2) - - # Click Edit - driver.find_element_by_css_selector(Global_Locators.Offering_edit_css).click() - - #Change name - driver.find_element_by_name(Global_Locators.Offering_editname_name).clear() - driver.find_element_by_name(Global_Locators.Offering_editname_name).send_keys("Test Name") - - # Change Description - driver.find_element_by_name(Global_Locators.Offering_editdescription_name).clear() - driver.find_element_by_name(Global_Locators.Offering_editdescription_name).send_keys("Test Description") - - #Click Done - driver.find_element_by_css_selector(Global_Locators.Offering_editdone_css).click() - time.sleep(10) - - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - self.assertEqual([], self.verificationErrors) - - # Now we will find this offering and delete it!! - - - - - - -class Disk_offering_Delete(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_diskdelete(self): - - driver = self.driver - self.driver.implicitly_wait(200) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Service Offerings - driver.find_element_by_xpath(Global_Locators.serviceOfferings_xpath).click() - - #Select Disk offering - driver.find_element_by_xpath(Global_Locators.Offering_disk_xpath).click() - - ## Action part - # We will be searching for our disk offering into the table - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.Offering_table_xpath) # This returns a list of all Offerings in table - - for link in linkclass: - - if link.text == "Test Name": - link.click() - - time.sleep(2) - - # Click Delete - driver.find_element_by_css_selector(Global_Locators.Offering_delete_css).click() - time.sleep(2) - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - time.sleep(20) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - - - - - - -class Compute_offering_Add(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_computeadd(self): - - driver = self.driver - self.driver.implicitly_wait(200) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Service Offerings - driver.find_element_by_xpath(Global_Locators.serviceOfferings_xpath).click() - - #Select Compute offering - driver.find_element_by_xpath(Global_Locators.Offering_compute_xpath).click() - - ## Action part - - # Add offering - driver.find_element_by_xpath(Global_Locators.Offering_add_xpath).click() - - # Following do not have Global locators - driver.find_element_by_id("label_name").clear() - driver.find_element_by_id("label_name").send_keys("Test Compute Name") - driver.find_element_by_id("label_description").clear() - driver.find_element_by_id("label_description").send_keys("Test Compute Description") - driver.find_element_by_id("label_num_cpu_cores").clear() - driver.find_element_by_id("label_num_cpu_cores").send_keys("2") - driver.find_element_by_id("label_cpu_mhz").clear() - driver.find_element_by_id("label_cpu_mhz").send_keys("2000") - driver.find_element_by_id("label_memory_mb").clear() - driver.find_element_by_id("label_memory_mb").send_keys("2048") - driver.find_element_by_id("label_network_rate").clear() - driver.find_element_by_id("label_network_rate").send_keys("10") - driver.find_element_by_id("label_offer_ha").click() - driver.find_element_by_xpath("//button[@type='button']").click() - - time.sleep(2) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - - time.sleep(30) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - - - - - -class Compute_offering_Edit(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_computeedit(self): - - - driver = self.driver - self.driver.implicitly_wait(200) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - ## Action part - # Go to Service Offerings - driver.find_element_by_xpath(Global_Locators.serviceOfferings_xpath).click() - - #Select Compute offering - driver.find_element_by_xpath(Global_Locators.Offering_compute_xpath).click() - - # We will be searching for our disk offering into the table - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.Offering_table_xpath) # This returns a list of all Offerings in table - - for link in linkclass: - - if link.text == "Test Compute Name": - link.click() - - time.sleep(2) - - - # Click Edit - driver.find_element_by_css_selector(Global_Locators.Offering_edit_css).click() - - #Change name - driver.find_element_by_name(Global_Locators.Offering_editname_name).clear() - driver.find_element_by_name(Global_Locators.Offering_editname_name).send_keys("Test Name") - - # Change Description - driver.find_element_by_name(Global_Locators.Offering_editdescription_name).clear() - driver.find_element_by_name(Global_Locators.Offering_editdescription_name).send_keys("Test Description") - - #Click Done - driver.find_element_by_css_selector(Global_Locators.Offering_editdone_css).click() - time.sleep(10) - - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - self.assertEqual([], self.verificationErrors) - - - - - - -class Compute_offering_Delete(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_computedelete(self): - - - driver = self.driver - self.driver.implicitly_wait(200) - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Service Offerings - driver.find_element_by_xpath(Global_Locators.serviceOfferings_xpath).click() - - #Select Compute offering - driver.find_element_by_xpath(Global_Locators.Offering_compute_xpath).click() - - ## Action part - # We will be searching for our disk offering into the table - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.Offering_table_xpath) # This returns a list of all Offerings in table - - for link in linkclass: - - if link.text == "Test Name": - link.click() - - time.sleep(2) - - # Click Delete - - driver.find_element_by_css_selector(Global_Locators.Offering_deletecompute_css).click() - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - - time.sleep(20) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) diff --git a/test/selenium/smoke/TemplatesAndISO.py b/test/selenium/smoke/TemplatesAndISO.py deleted file mode 100644 index c69b44691166..000000000000 --- a/test/selenium/smoke/TemplatesAndISO.py +++ /dev/null @@ -1,244 +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. - -''' -ISO PART YET TO BE ADDED:: remove this after adding it. -''' - -import sys, os -sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/'+'../lib')) - - - -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import Select -from selenium.common.exceptions import NoSuchElementException -import unittest, time -import initialize -import Global_Locators - - - - -class Template_Add(unittest.TestCase): - - - - def setUp(self): - - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_templateadd(self): - - - driver = self.driver - - ## Action part - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Templates - driver.find_element_by_xpath(Global_Locators.templates_xpath).click() - - #Select Template from drop down list - driver.find_element_by_xpath(Global_Locators.template_xpath).click() - - # Add Template - driver.find_element_by_xpath(Global_Locators.AddTemplate_xpath).click() - - # Following have names.. so they do not have their global entries. - driver.find_element_by_id("label_name").clear() - driver.find_element_by_id("label_name").send_keys("Test Template Ubuntu") - driver.find_element_by_id("label_description").clear() - driver.find_element_by_id("label_description").send_keys("Ubuntu 10.04") - driver.find_element_by_id("URL").clear() - driver.find_element_by_id("URL").send_keys("http://nfs1.lab.vmops.com/templates/Ubuntu/Ubuntuu-10-04-64bit-server.vhd") - Select(driver.find_element_by_id("label_os_type")).select_by_visible_text("Ubuntu 10.04 (64-bit)") - driver.find_element_by_id("label_public").click() - driver.find_element_by_id("label_featured").click() - driver.find_element_by_xpath("//button[@type='button']").click() - - time.sleep(2) - - # Go to Dash Board - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - - - time.sleep(600) - - ##Verification will be if this offering shows up into table and we can actually edit it. - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - - - - - -class Template_Edit(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_templateedit(self): - - driver = self.driver - - ## Action part - - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Templates - driver.find_element_by_xpath(Global_Locators.templates_xpath).click() - - #Select Template from drop down list - driver.find_element_by_xpath(Global_Locators.template_xpath).click() - - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.template_table_xpath) # This returns a list - - for link in linkclass: - - if link.text == "Test Template Ubuntu": # We will search for our VM in this table - link.click() - - time.sleep(2) - - # Change name - driver.find_element_by_name("name").clear() - driver.find_element_by_name("name").send_keys("Test template") - - - # Change Description - driver.find_element_by_name("displaytext").clear() - driver.find_element_by_name("displaytext").send_keys("ubuntu") - - driver.find_element_by_css_selector(Global_Locators.template_editdone_css).click() - time.sleep(2) - - #Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(10) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - -# Now we will find this offering and delete it!! - - - - - - -class Template_Delete(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - - def test_templatedelete(self): - - driver = self.driver - - ## Action part - #Make sure you are on Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - # Go to Templates - driver.find_element_by_xpath(Global_Locators.templates_xpath).click() - - #Select Template from drop down list - driver.find_element_by_xpath(Global_Locators.template_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.template_table_xpath) # This returns a list - - for link in linkclass: - - if link.text == "Test Template": # We will search for our VM in this table - link.click() - - time.sleep(2) - - driver.find_element_by_css_selector(Gloabl_Locators.template_delete_css).click() - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - - time.sleep(2) - - #Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - - time.sleep(20) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) diff --git a/test/selenium/smoke/VM_lifeCycle.py b/test/selenium/smoke/VM_lifeCycle.py deleted file mode 100644 index 480c45c8237c..000000000000 --- a/test/selenium/smoke/VM_lifeCycle.py +++ /dev/null @@ -1,613 +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. - -import sys, os -sys.path.append(os.path.abspath(os.path.dirname(__file__) + '/'+'../lib')) - - - -from selenium import webdriver -from selenium.webdriver.common.by import By -from selenium.webdriver.support.ui import Select -from selenium.common.exceptions import NoSuchElementException -import unittest, time -import initialize -import Global_Locators - - - -class deployVM(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_deployvm(self): - - - ## Action Part - # VM will be named Auto-VM and this VM will be used in all subsequent tests. - # Deploy an Instance named Auto-VM Default CentOS no GUI Template - - driver = self.driver - self.driver.implicitly_wait(30) - driver.refresh() ## Most Important step. Failure to do this will change XPATH location and Scripts will fail. - - - # Click on Instances link - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - # Click on add Instance on Instances page - driver.find_element_by_xpath(Global_Locators.add_instance_xpath).click() - - # Following select template action will fire automatically... ignore it. And leave following commented. - # driver.find_element_by_xpath("(//input[@name='select-template'])[3]").click() - #Click on Next button on Instances Wizard. - driver.find_element_by_xpath(Global_Locators.add_instance_next_xpath).click() - - # Nothing to do here as we will be using all default settings. (Default CentOS no GUI template should be highlighted here. Click Next - driver.find_element_by_xpath(Global_Locators.add_instance_next_xpath).click() - - # Nothing to do here. Medium Instance compute offering should be selected here. Click Next - driver.find_element_by_xpath(Global_Locators.add_instance_next_xpath).click() - - # Nothing to do here. Data Disk Offering : No Thanks!!. Click Next - driver.find_element_by_xpath(Global_Locators.add_instance_next_xpath).click() - - # Since this is our first instance; we must provide a network name. We will use Test-Network as out network name. - driver.find_element_by_xpath("(//input[@name='new-network-name'])[2]").click() - driver.find_element_by_xpath("(//input[@name='new-network-name'])[2]").clear() - driver.find_element_by_xpath("(//input[@name='new-network-name'])[2]").send_keys("Test-Network") - - #Click next - driver.find_element_by_xpath(Global_Locators.add_instance_next_xpath).click() - - # Give our VM a name here. Use Auto-VM as name - driver.find_element_by_xpath("(//input[@name='displayname'])[2]").click() - - driver.find_element_by_xpath("(//input[@name='displayname'])[2]").clear() - - driver.find_element_by_xpath("(//input[@name='displayname'])[2]").send_keys("Auto-VM") - - # All data filled. Click Launch VM. (It has the same xpath as Next button. So we will use Next Variable here. - driver.find_element_by_xpath(Global_Locators.add_instance_next_xpath).click() - - print '\n' + '\n' + "VM Deployment is complete... wait for 5 mins to check deployment status" + '\n' + '\n' - - - - ## Verification Part - - - ## Now we must wait for some random time (Educated guess based on experience) and check if VM has been deployed and if it is in running state. - ## Should take about 4 min to deploy VM.. but we will wait 5 mins and check the status , we will do this twice. So total 2 check within 10 mins with first check occuring at 5th min. - - - driver.refresh() # Refresh UI Page; This polls latest status. - - # Click on Instances link - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - count = 1 - - while (count > 0): - - time.sleep(300) - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - print "found VM in table .. checking status..." + '\n' + '\n' - link.click() - - status = driver.find_element_by_xpath(Global_Locators.state_xpath).text ## get the status of our VM - - if status == "Running" : - print "VM is in running state... continuing with other tests."+ '\n' + '\n' - break - else: - print "Need to check one more time after 5 mins" - continue - count = count - 1 - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - - def tearDown(self): - self.assertEqual([], self.verificationErrors) - - - - - -################################################################################################################################################################################################ - - - -class destroyVM(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_destroyvm(self): - - driver = self.driver - self.driver.implicitly_wait(100) - - ## Action part - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - time.sleep(2) - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - link.click() - - # Click on Destroy Instance button and confirm - time.sleep(2) - driver.find_element_by_css_selector(Global_Locators.destroyinstance_css).click() - time.sleep(2) - - # Click ok on confirmation - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - time.sleep(2) - - # Go to Dashboard - # driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - driver.refresh() - - ## Verification part - time.sleep(60) - - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - time.sleep(2) - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - link.click() - - - status = driver.find_element_by_xpath(Global_Locators.state_xpath).text ## get the status of our VM - if status == "Destroyed" : - print "VM is Destroyed...."+ '\n' + '\n' - else: - print "Something went wrong" - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - - - -################################################################################################################################################################################################ - - - - -class rebootVM(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_rebootvm(self): - - driver = self.driver - self.driver.implicitly_wait(30) - print "Verify this test manually for now" - - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(vmLifeAndNetwork.Server_Ip, username='root', password='password') - print '\n' + '\n' + "Before Reboot ...Executing command date ... " + '\n' + '\n' - stdin, stdout, stderr = ssh.exec_command('date') - print stdout.readlines() - print '\n' + '\n' + "Before Reboot ...Executing command last reboot | head -1 ..." + '\n' + '\n' - stdin, stdout, stderr = ssh.exec_command('last reboot | head -1') - print '\n' + '\n' + "Before Reboot ...Executing command uptime..." + '\n' + '\n' - stdin, stdout, stderr = ssh.exec_command('uptime') - print stdout.readlines() - ssh.close() - - - driver.refresh() - - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - count = 1 - - while (count > 0): - - #time.sleep(300) - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - print "found VM in table .. Rebooting now..." + '\n' + '\n' - link.click() - - driver.find_element_by_css_selector(Global_Locators.rebootinstance_css).click() - driver.find_element_by_xpath(Global_Locators.actionconfirm_xpath).click() - - # Sleep for 5 mins to ensure system gets rebooted. - time.sleep(300) - - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - ssh.connect(vmLifeAndNetwork.Server_Ip, username='root', password='password') - print '\n' + '\n' + "After Reboot ...Executing command date ... " + '\n' + '\n' - stdin, stdout, stderr = ssh.exec_command('date') - print stdout.readlines() - print '\n' + '\n' + "After Reboot ...Executing command last reboot | head -1 ..." + '\n' + '\n' - stdin, stdout, stderr = ssh.exec_command('last reboot | head -1') - print '\n' + '\n' + "After Reboot ...Executing command uptime..." + '\n' + '\n' - stdin, stdout, stderr = ssh.exec_command('uptime') - print stdout.readlines() - ssh.close() - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - def tearDown(self): - self.assertEqual([], self.verificationErrors) - - -######################################################################################################################################################### - - - -class restoreVM(unittest.TestCase): - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_restorevm(self): - - driver = self.driver - self.driver.implicitly_wait(100) - - ## Action part - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - - link.click() - - # Click on Destroy Instance button and confirm - driver.find_element_by_css_selector(Global_Locators.restoreinstance_css).click() - - # Click ok on confirmation - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - - # Go to Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - - - ## Verification part - - time.sleep(60) - - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - link.click() - - - status = driver.find_element_by_xpath(Global_Locators.state_xpath).text ## get the status of our VM - - if status == "Stopped" : - print "VM is Restored. but in stopped state.. will start now."+ '\n' + '\n' - - else: - print "Something went wrong" - - - - - #VM will be in stop state so we must start it now - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - link.click() - - # Click on Start Instance. - driver.find_element_by_css_selector(Global_Locators.startinstance_css).click() - time.sleep(2) - - # Dismiss confirmation by clicking Yes - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - time.sleep(2) - - # Go to Dashboard - driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - time.sleep(2) - - print "VM is Started."+ '\n' + '\n' - - # status = None - time.sleep(60) - - # Dismiss the Start Instance information box. - driver.find_element_by_xpath(Global_Locators.actionconfirm_xpath).click() - time.sleep(2) - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - -######################################################################################################################################################### - - - -class startVM(unittest.TestCase): - - - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_startvm(self): - - driver = self.driver - self.driver.implicitly_wait(100) - - ## Action part - #driver.refresh() ## Most Important step. Failure to do this will change XPATH location and Scripts will fail. - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - print "found VM in table .. checking status..." + '\n' + '\n' - link.click() - - - - # Click on Start Instance. - driver.find_element_by_css_selector(Global_Locators.startinstance_css).click() - time.sleep(2) - - # Dismiss confirmation by clicking Yes - driver.find_element_by_xpath(Global_Locators.yesconfirmation_xapth).click() - time.sleep(2) - - # Go to Dashboard - #driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - driver.refresh() - - - ## Verification part - # status = None - time.sleep(60) - - # Dismiss the Start Instance information box. - driver.find_element_by_xpath(Global_Locators.actionconfirm_xpath).click() - time.sleep(2) - - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - time.sleep(2) - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - link.click() - - - status = driver.find_element_by_xpath(Global_Locators.state_xpath).text ## get the status of our VM - - if status == "Running" : - print "VM is in Running state..."+ '\n' + '\n' - - else: - print "Something went wrong" - - # Go to Dashboard - driver.refresh() - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - - - -######################################################################################################################################################### - - - -class stopVM(unittest.TestCase): - - def setUp(self): - - self.driver = initialize.getOrCreateWebdriver() - self.verificationErrors = [] - - - def test_stopvm(self): - - driver = self.driver - self.driver.implicitly_wait(100) - - ## Action part - driver.refresh() ## Important step. - - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - print "found VM in table .. checking status..." + '\n' + '\n' - link.click() - - - # HWe are on our VM information page. - driver.find_element_by_css_selector(Global_Locators.stopinstance_css).click() - time.sleep(2) - - # a Pop up must appear; below we will check the force stop check box and then we will click ok. - driver.find_element_by_id(Global_Locators.stopinstanceforce_id).click() - driver.find_element_by_xpath(Global_Locators.actionconfirm_xpath).click() - time.sleep(2) - - # Go to Dahsboard - #driver.find_element_by_xpath(Global_Locators.dashboard_xpath).click() - driver.refresh() - - # Should take less than min to stop the instance. We will check twice at interval of 45 seconds o be safe. - ## Verification part - time.sleep(60) - - # Click on Instances link and find our instance - driver.find_element_by_xpath(Global_Locators.instances_xpath).click() - - linkclass = None - linkclass = driver.find_elements_by_xpath(Global_Locators.instances_table_xpath) # This returns a list of all VM names in tables - - for link in linkclass: - - if link.text == "Auto-VM": # We will search for our VM in this table - link.click() - - - status = driver.find_element_by_xpath(Global_Locators.state_xpath).text ## get the status of our VM - - if status == "Stopped" : - print "VM is in Stopped state...."+ '\n' + '\n' - else: - print "Something went wrong" - - - - def is_element_present(self, how, what): - - try: self.driver.find_element(by=how, value=what) - except NoSuchElementException, e: return False - return True - - - - def tearDown(self): - - self.assertEqual([], self.verificationErrors) - - -######################################################################################################################################################### diff --git a/test/selenium/smoke/main.py b/test/selenium/smoke/main.py deleted file mode 100644 index c24d6700057a..000000000000 --- a/test/selenium/smoke/main.py +++ /dev/null @@ -1,142 +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. - -import unittest -import HTMLTestRunner -import xmlrunner - - -global DRIVER -global MS_ip - -# Import test cases - -################################## -from Login_and_Accounts import * -from Service_Offering import * - -from TemplatesAndISO import * -from VM_lifeCycle import * - -################################### - - -# Following are BVT Tests -# serialize the test cases - - -suite = unittest.TestSuite() # setup new test suite - - -#################################################################################################### - -# Following logs admin user in and creates test account then logs admin user out and logs in as test to run tests. -# You should leave this as is for all the tests. - -suite.addTest(unittest.makeSuite(login)) #Login Admin - -time.sleep(5) -suite.addTest(unittest.makeSuite(createAcc)) # Create an Account test. We will use test account for all our tests - -time.sleep(5) -suite.addTest(unittest.makeSuite(logout)) #Logout Admin - -time.sleep(5) -suite.addTest(unittest.makeSuite(login_test)) # Login Test - - - -#################################################################################################### - - - -time.sleep(5) -suite.addTest(unittest.makeSuite(Disk_offering_Add)) - -time.sleep(5) -suite.addTest(unittest.makeSuite(Disk_offering_Edit)) - -time.sleep(5) -suite.addTest(unittest.makeSuite(Disk_offering_Delete)) - -time.sleep(5) -suite.addTest(unittest.makeSuite(Compute_offering_Add)) - -time.sleep(5) -suite.addTest(unittest.makeSuite(Compute_offering_Edit)) - -time.sleep(5) -suite.addTest(unittest.makeSuite(Compute_offering_Delete)) - - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(deployVM)) - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(stopVM)) - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(startVM)) - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(destroyVM)) - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(restoreVM)) - - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(Template_Add)) - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(Template_Edit)) - -# time.sleep(5) -# suite.addTest(unittest.makeSuite(Template_Delete)) - - -#################################################################################################### - -# Following logs test user out and logs back in as Admin and tears down the test account. -# You should leave this as is for all the tests. - -suite.addTest(unittest.makeSuite(logout)) #Logout test -time.sleep(5) -suite.addTest(unittest.makeSuite(login)) #Login Admin -time.sleep(5) -suite.addTest(unittest.makeSuite(tearAcc)) # Delete Account test - -#################################################################################################### - - - -# If XML reports compatible with junit's XML output are desired then leave folowing code as is. -# If HTML reports are desired follow instructions - - -#Comment following line for HTML and uncomment for XML -runner = xmlrunner.XMLTestRunner(output='test-reports') - -#Comment following line for XML and uncomment for HTML -#runner = HTMLTestRunner.HTMLTestRunner() - -#header is required for displaying the website -#Comment following line for XML and uncomment for HTML -#print "content-type: text/html\n" - -# Leave following as is for either XML or HTML -runner.run(suite) diff --git a/test/systemvm/README.md b/test/systemvm/README.md index ad84037a9589..89e79c2ebffd 100644 --- a/test/systemvm/README.md +++ b/test/systemvm/README.md @@ -1,5 +1,24 @@ -Requirements -============ + + +# Requirements + To run these tests, first get the vagrant setup for the systemvm working, see ../../tools/vagrant/systemvm. @@ -7,8 +26,8 @@ Then, install dependencies pip install nose paramiko python-vagrant envassert cuisine fabric -Running tests -============= +## Running tests + Then run the tests using your favorite python unittest runner nosetests-2.7 @@ -22,8 +41,8 @@ not recommended. You can also run these tests out of the box with PyDev or PyCharm or whatever. -Adding tests -============ +## Adding tests + Simply create new test_xxx.py files with test cases that extend from SystemVMTestCase. @@ -55,8 +74,8 @@ class HelloSystemVMTestCase(SystemVMTestCase): assert something_to_do('foo') ``` -Edit, test, edit, test -====================== +## Edit, test, edit, test + The SystemVM Vagrantfile sets up rsync from systemvm/patches. These rsyncs run once, when you type 'vagrant up'. To do these rsyncs every time you change a patch file, run 'vagrant rsync-auto'. With that, your development process can diff --git a/tools/apidoc/gen_toc.py b/tools/apidoc/gen_toc.py index 8d28749a637b..c99328fff9ff 100644 --- a/tools/apidoc/gen_toc.py +++ b/tools/apidoc/gen_toc.py @@ -56,6 +56,8 @@ 'HypervisorGuestOsNames': 'Guest OS', 'Domain': 'Domain', 'Template': 'Template', + 'KMS': 'Key Management System', + 'HSM': 'Key Management System', 'Iso': 'ISO', 'Volume': 'Volume', 'Vlan': 'VLAN', @@ -98,6 +100,7 @@ 'listNsxControllers': 'NSX', 'addNsxController': 'NSX', 'deleteNsxController': 'NSX', + 'NetrisProvider': 'Netris', 'Vpn': 'VPN', 'Limit': 'Resource Limit', 'Netscaler': 'Netscaler', @@ -173,6 +176,7 @@ 'removeIpFromNic': 'Nic', 'updateVmNicIp': 'Nic', 'listNics':'Nic', + 'updateVmNic': 'Nic', 'AffinityGroup': 'Affinity Group', 'ImageStore': 'Image Store', 'addImageStore': 'Image Store', @@ -221,19 +225,26 @@ 'Management': 'Management', 'Backup' : 'Backup and Recovery', 'Restore' : 'Backup and Recovery', + 'startBackup' : 'Backup and Recovery', + 'finalizeBackup' : 'Backup and Recovery', + 'ImageTransfer' : 'Backup and Recovery', + 'VmCheckpoint' : 'Backup and Recovery', 'UnmanagedInstance': 'Virtual Machine', 'KubernetesSupportedVersion': 'Kubernetes Service', 'KubernetesCluster': 'Kubernetes Service', 'Rolling': 'Rolling Maintenance', 'importVsphereStoragePolicies' : 'vSphere storage policies', 'listVsphereStoragePolicies' : 'vSphere storage policies', - 'ConsoleEndpoint': 'Console Endpoint', + 'createConsoleEndpoint': 'Console Session', + 'listConsoleSessions': 'Console Session', 'importVm': 'Virtual Machine', 'revertToVMSnapshot': 'Virtual Machine', 'listQuarantinedIp': 'IP Quarantine', 'updateQuarantinedIp': 'IP Quarantine', 'removeQuarantinedIp': 'IP Quarantine', - 'Shutdown': 'Management', + 'Shutdown': 'Maintenance', + 'Maintenance': 'Maintenance', + 'ResourceSchedule': 'Resource Schedule', 'addObjectStoragePool': 'Object Store', 'listObjectStoragePools': 'Object Store', 'deleteObjectStoragePool': 'Object Store', @@ -256,6 +267,23 @@ 'deleteASNRange': 'AS Number Range', 'listASNumbers': 'AS Number', 'releaseASNumber': 'AS Number', + 'addNodesToKubernetesCluster': 'Kubernetes Service', + 'removeNodesFromKubernetesCluster': 'Kubernetes Service', + 'configureStorageAccess': 'Storage Access Groups', + 'listStorageAccessGroups': 'Storage Access Groups', + 'listGuiThemes': 'GUI Theme', + 'createGuiTheme': 'GUI Theme', + 'updateGuiTheme': 'GUI Theme', + 'removeGuiTheme': 'GUI Theme', + 'Gpu': 'GPU', + 'Vgpu': 'GPU', + 'Extension' : 'Extension', + 'Extensions' : 'Extension', + 'CustomAction' : 'Extension', + 'CustomActions' : 'Extension', + 'ImportVmTask': 'Import VM Task', + 'Dns': 'DNS', + 'downloadValidationScreenshot': 'Backup and Recovery' } diff --git a/tools/apidoc/generatecommand.xsl b/tools/apidoc/generatecommand.xsl index e610d092bad9..9cc221022005 100644 --- a/tools/apidoc/generatecommand.xsl +++ b/tools/apidoc/generatecommand.xsl @@ -36,17 +36,17 @@ version="1.0">
- +
- +
@@ -62,11 +62,11 @@ version="1.0">
- +
- +
@@ -77,12 +77,12 @@ version="1.0">
- +
-
+

Request parameters

@@ -101,16 +101,16 @@ version="1.0">
- - -
+ + +

Response Tags

- + @@ -125,24 +125,24 @@ version="1.0"> - - + + - - - - - + + + + +
Response Name Description
- - -
+ +
- - +
+ +
+ - - + + +